Add additional email parsing and tests

Added additional attribute parsing and corresponding unit-tests.
E-mail attachment and url extraction added in this commit. This includes
unpacking zipfiles and simple password cracking of encrypted zipfiles.
pull/62/head
seamus tuohy 2016-12-26 14:38:28 -08:00
parent 0ff270a3be
commit 1a7973bc06
9 changed files with 373 additions and 529 deletions

View File

@ -21,12 +21,10 @@ moduleinfo = {'version': '0.1',
'description': 'Email import module for MISP', 'description': 'Email import module for MISP',
'module-type': ['import']} 'module-type': ['import']}
# treat_attachments_as_malware : This treats all attachments as malware. This will zip all attachments and password protect using the password 'infected'
# unzip_attachments : Unzip all zip files that are not password protected # unzip_attachments : Unzip all zip files that are not password protected
# guess_zip_attachment_passwords : This attempts to unzip all password protected zip files using all the strings found in the email body and subject # guess_zip_attachment_passwords : This attempts to unzip all password protected zip files using all the strings found in the email body and subject
# extract_urls : This attempts to extract all URL's from text/html parts of the email # extract_urls : This attempts to extract all URL's from text/html parts of the email
moduleconfig = ["treat_attachments_as_malware", moduleconfig = ["unzip_attachments",
"unzip_attachments",
"guess_zip_attachment_passwords", "guess_zip_attachment_passwords",
"extract_urls"] "extract_urls"]
@ -45,7 +43,7 @@ def handler(q=False):
# Extract all header information # Extract all header information
all_headers = "" all_headers = ""
for k, v in message.items(): for k, v in message.items():
all_headers += "\n{0}: {1}".format(k, v) all_headers += "{0}: {1}\n".format(k, v)
results.append({"values": all_headers, results.append({"values": all_headers,
"types": ['email-header']}) "types": ['email-header']})
@ -77,14 +75,10 @@ def handler(q=False):
from_addr = message.get('From') from_addr = message.get('From')
results.append({"values": parseaddr(from_addr)[1], results.append({"values": parseaddr(from_addr)[1],
"types": ['email-src'], "types": ['email-src'],
"comment": "From: {0}".format(re.sub('["\']', "comment": "From: {0}".format(from_addr)})
'', results.append({"values": parseaddr(from_addr)[0],
from_addr))})
results.append({"values": parseaddr(from_addr)[1],
"types": ['email-src-display-name'], "types": ['email-src-display-name'],
"comment": "From: {0}".format(re.sub('["\']', "comment": "From: {0}".format(from_addr)})
'',
from_addr))})
# Return Path # Return Path
return_path = message.get('Return-Path') return_path = message.get('Return-Path')
@ -111,15 +105,11 @@ def handler(q=False):
results.append({"values": parsed_addr[1], results.append({"values": parsed_addr[1],
"types": ["email-dst"], "types": ["email-dst"],
"comment": "{0}: {1}".format(hdr_val, "comment": "{0}: {1}".format(hdr_val,
re.sub('["\']', addr)})
'',
addr))})
results.append({"values": parsed_addr[0], results.append({"values": parsed_addr[0],
"types": ["email-dst-display-name"], "types": ["email-dst-display-name"],
"comment": "{0}: {1}".format(hdr_val, "comment": "{0}: {1}".format(hdr_val,
re.sub('["\']', addr)})
'',
addr))})
except AttributeError: except AttributeError:
continue continue
@ -127,45 +117,45 @@ def handler(q=False):
# Get E-Mail Targets # Get E-Mail Targets
# Get the addresses that received the email. # Get the addresses that received the email.
# As pulled from the Received header # As pulled from the Received header
received = message.get_all('received') received = message.get_all('Received')
email_targets = set() email_targets = set()
for rec in received: try:
try: for rec in received:
email_check = re.search("for\s(.*@.*);", rec).group(1) try:
email_check = email_check.strip(' <>') email_check = re.search("for\s(.*@.*);", rec).group(1)
email_targets.add(parseaddr(email_check)[1]) email_check = email_check.strip(' <>')
except (AttributeError): email_targets.add(parseaddr(email_check)[1])
continue except (AttributeError):
for tar in email_targets: continue
results.append({"values": tar, for tar in email_targets:
"types": ["target-email"], results.append({"values": tar,
"comment": "Extracted from email 'Received' header"}) "types": ["target-email"],
"comment": "Extracted from email 'Received' header"})
except TypeError:
pass # If received header is missing we can't iterate over NoneType
# Check if we were given a configuration # Check if we were given a configuration
config = request.get("config", {}) config = request.get("config", {})
# Don't be picky about how the user chooses to say yes to these # Don't be picky about how the user chooses to say yes to these
acceptable_config_yes = ['y', 'yes', 'true', 't'] acceptable_config_yes = ['y', 'yes', 'true', 't']
# Do we treat all attachments as malware
treat_attachments_as_malware = config.get("treat_attachments_as_malware",
"false")
if treat_attachments_as_malware.lower() in acceptable_config_yes:
treat_attachments_as_malware = True
# Do we unzip attachments we find? # Do we unzip attachments we find?
unzip = config.get("unzip_attachments", "false") unzip = config.get("unzip_attachments", None)
if unzip.lower() in acceptable_config_yes: if (unzip is not None and
unzip.lower() in acceptable_config_yes):
unzip = True unzip = True
# Do we try to find passwords for protected zip files? # Do we try to find passwords for protected zip files?
zip_pass_crack = config.get("guess_zip_attachment_passwords", "false") zip_pass_crack = config.get("guess_zip_attachment_passwords", None)
if zip_pass_crack.lower() in acceptable_config_yes: if (zip_pass_crack is not None and
zip_pass_crack.lower() in acceptable_config_yes):
zip_pass_crack = True zip_pass_crack = True
password_list = None # Only want to collect password list once password_list = None # Only want to collect password list once
# Do we extract URL's from the email. # Do we extract URL's from the email.
extract_urls = config.get("extract_urls", "false") extract_urls = config.get("extract_urls", None)
if extract_urls.lower() in acceptable_config_yes: if (extract_urls is not None and
extract_urls.lower() in acceptable_config_yes):
extract_urls = True extract_urls = True
# Get Attachments # Get Attachments
@ -174,41 +164,35 @@ def handler(q=False):
filename = part.get_filename() filename = part.get_filename()
if filename is not None: if filename is not None:
attachment_data = part.get_payload(decode=True) attachment_data = part.get_payload(decode=True)
# Base attachment data is default
attachment_files = [{"values": filename,
"data" : base64.b64encode(attachment_data).decode()}]
if unzip is True: # Attempt to unzip the attachment and return its files if unzip is True: # Attempt to unzip the attachment and return its files
try: try:
attachment_files = get_zipped_contents(filename, attachment_files += get_zipped_contents(filename,
attachment_data) attachment_data)
except RuntimeError: # File is encrypted with a password except RuntimeError: # File is encrypted with a password
if zip_pass_crack is True: if zip_pass_crack is True:
if password_list is None: if password_list is None:
password_list = get_zip_passwords(message) password_list = get_zip_passwords(message)
password = test_zip_passwords(attachment_data, password_list) password = test_zip_passwords(attachment_data, password_list)
# If we don't guess the password just use the zip if password is None: # Inform the analyst that we could not crack password
if password is None: attachment_files[0]['comment'] = "Encrypted Zip: Password could not be cracked from message"
attachment_files = [{"values": filename,
"data" : base64.b64encode(attachment_data),
"comment":"Password could not be cracked from message"}]
else: else:
attachment_files = get_zipped_contents(filename, attachment_files[0]['comment'] = """Original Zipped Attachment with Password {0}""".format(password)
attachment_files += get_zipped_contents(filename,
attachment_data, attachment_data,
password=password) password=password)
except zipfile.BadZipFile: # Attachment is not a zipfile except zipfile.BadZipFile: # Attachment is not a zipfile
attachment_files = [{"values": filename, attachment_files += [{"values": filename,
"data" : base64.b64encode(attachment_data)}] "data" : base64.b64encode(attachment_data).decode()}]
else:
attachment_files = [{"values": filename,
"data" : base64.b64encode(attachment_data)}]
for attch_item in attachment_files: for attch_item in attachment_files:
if treat_attachments_as_malware is True: # Malware-samples are encrypted by server attch_item["types"] = ['attachment']
attch_item["types"] = ['malware-sample']
else:
attch_item["types"] = ['attachment']
results.append(attch_item) results.append(attch_item)
else: # Check email body part for urls else: # Check email body part for urls
if (extract_urls is True and part.get_content_type() == 'text/html'): if (extract_urls is True and part.get_content_type() == 'text/html'):
url_parser = HTMLURLParser() url_parser = HTMLURLParser()
charset = get_charset(i, get_charset(message)) charset = get_charset(part, get_charset(message))
url_parser.feed(part.get_payload(decode=True).decode(charset)) url_parser.feed(part.get_payload(decode=True).decode(charset))
urls = url_parser.urls urls = url_parser.urls
for url in urls: for url in urls:
@ -235,11 +219,11 @@ def get_zipped_contents(filename, data, password=None):
unzipped_files = [] unzipped_files = []
if password is not None: if password is not None:
password = str.encode(password) # Byte encoded password required password = str.encode(password) # Byte encoded password required
for zip_file_name in zf: # Get all files in the zip file for zip_file_name in zf.namelist(): # Get all files in the zip file
with zf.open(zip_file_name, mode='rU', pwd=password) as fp:
file_data = fp.read()
unzipped_files.append({"values": zip_file_name, unzipped_files.append({"values": zip_file_name,
"data" : base64.b64encode(zf.open(zip_file_name, "data" : base64.b64encode(file_data).decode(), # Any password works when not encrypted
mode='rU',
pwd=password)), # Any password works when not encrypted
"comment": "Extracted from {0}".format(filename)}) "comment": "Extracted from {0}".format(filename)})
return unzipped_files return unzipped_files
@ -256,11 +240,12 @@ def test_zip_passwords(data, test_passwords):
""" """
with zipfile.ZipFile(io.BytesIO(data), "r") as zf: with zipfile.ZipFile(io.BytesIO(data), "r") as zf:
firstfile = zf.namelist()[0]
for pw_test in test_passwords: for pw_test in test_passwords:
byte_pwd = str.encode(pw_test) byte_pwd = str.encode(pw_test)
try: try:
zf.testzip() zf.open(firstfile, pwd=byte_pwd)
return byte_pwd return pw_test
except RuntimeError: # Incorrect Password except RuntimeError: # Incorrect Password
continue continue
return None return None
@ -315,10 +300,10 @@ def get_zip_passwords(message):
raw_text += subject raw_text += subject
# Grab any strings that are marked off by special chars # Grab any strings that are marked off by special chars
marking_chars = [["'", "'"], ['"', '"'], ['[', ']'], ['(', ')']] marking_chars = [["\'", "\'"], ['"', '"'], ['[', ']'], ['(', ')']]
for char_set in marking_chars: for char_set in marking_chars:
regex = re.compile("'{0}([^{1}]*){1}'".format(char_set[0], regex = re.compile("""\{0}([^\{1}]*)\{1}""".format(char_set[0],
char_set[1])) char_set[1]))
marked_off = re.findall(regex, raw_text) marked_off = re.findall(regex, raw_text)
possible_passwords += marked_off possible_passwords += marked_off
@ -350,7 +335,7 @@ class HTMLURLParser(HTMLParser):
if urls is None: if urls is None:
self.urls = [] self.urls = []
else: else:
self.urls = output_list self.urls = urls
def handle_starttag(self, tag, attrs): def handle_starttag(self, tag, attrs):
if tag == 'a': if tag == 'a':
self.urls.append(dict(attrs).get('href')) self.urls.append(dict(attrs).get('href'))

1
tests/EICAR.com Normal file
View File

@ -0,0 +1 @@
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-

BIN
tests/EICAR.com.zip Normal file

Binary file not shown.

BIN
tests/infected.zip Normal file

Binary file not shown.

BIN
tests/longer_password.zip Normal file

Binary file not shown.

BIN
tests/short_password.zip Normal file

Binary file not shown.

View File

@ -5,8 +5,11 @@ import unittest
import requests import requests
import base64 import base64
import json import json
import os import io
import urllib import zipfile
from email.mime.application import MIMEApplication
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
class TestModules(unittest.TestCase): class TestModules(unittest.TestCase):
@ -53,164 +56,298 @@ class TestModules(unittest.TestCase):
assert("eu-society.com" in values) assert("eu-society.com" in values)
def test_email_headers(self): def test_email_headers(self):
with open("tests/test_no_attach.eml", "r") as f: query = {"module":"email_import"}
data = json.dumps({"module":"email_import", query["config"] = {"unzip_attachments": None,
"data":str(base64.b64encode(bytes(f.read(), 'utf8')), "guess_zip_attachment_passwords": None,
'utf8')}).encode('utf8') "extract_urls": None}
response = requests.post(self.url + "query", data=data) message = get_base_email()
response.connection.close() text = """I am a test e-mail"""
print(response.json()) message.attach(MIMEText(text, 'plain'))
query['data'] = decode_email(message)
data = json.dumps(query)
response = requests.post(self.url + "query", data=data)
results = response.json()['results']
values = [x["values"] for x in results]
types = {}
for i in results:
types.setdefault(i["types"][0], 0)
types[i["types"][0]] += 1
# Check that there are the appropriate number of items
# Check that all the items were correct
self.assertEqual(types['target-email'], 1)
self.assertIn('test@domain.com', values)
self.assertEqual(types['email-dst-display-name'], 4)
self.assertIn('Last One', values)
self.assertIn('Other Friend', values)
self.assertIn('Second Person', values)
self.assertIn('Testy Testerson', values)
self.assertEqual(types['email-dst'], 4)
self.assertIn('test@domain.com', values)
self.assertIn('second@domain.com', values)
self.assertIn('other@friend.net', values)
self.assertIn('last_one@finally.com', values)
self.assertEqual(types['email-src-display-name'], 2)
self.assertIn("Innocent Person", values)
self.assertEqual(types['email-src'], 2)
self.assertIn("evil_spoofer@example.com", values)
self.assertIn("IgnoreMeImInnocent@sender.com", values)
self.assertEqual(types['email-thread-index'], 1)
self.assertIn('AQHSR8Us3H3SoaY1oUy9AAwZfMF922bnA9GAgAAi9s4AAGvxAA==', values)
self.assertEqual(types['email-message-id'], 1)
self.assertIn("<4988EF2D.40804@example.com>", values)
self.assertEqual(types['email-subject'], 1)
self.assertIn("Example Message", values)
self.assertEqual(types['email-header'], 1)
self.assertEqual(types['email-x-mailer'], 1)
self.assertIn("mlx 5.1.7", values)
self.assertEqual(types['email-reply-to'], 1)
# The parser inserts a newline that I can't diagnose.
# It does not impact analysis since the interface strips it.
# But, I'm leaving this test failing
self.assertIn("<CI7DgL-A6dm92s7gf4-88g@E_0x238G4K2H08H9SDwsw8b6LwuA@mail.example.com>", values)
#self.assertIn("\n <CI7DgL-A6dm92s7gf4-88g@E_0x238G4K2H08H9SDwsw8b6LwuA@mail.example.com>", values)
def test_email_attachment_basic(self): def test_email_attachment_basic(self):
with open("tests/test_attachment.eml", "r") as f: query = {"module":"email_import"}
data = json.dumps({"module":"email_import", query["config"] = {"unzip_attachments": None,
"data":str(base64.b64encode(bytes(f.read(), 'utf8')), "guess_zip_attachment_passwords": None,
'utf8')}).encode('utf8') "extract_urls": None}
response = requests.post(self.url + "query", data=data) message = get_base_email()
response.connection.close() text = """I am a test e-mail"""
print(response.json()) message.attach(MIMEText(text, 'plain'))
with open("tests/EICAR.com", "rb") as fp:
eicar_mime = MIMEApplication(fp.read(), 'com')
eicar_mime.add_header('Content-Disposition', 'attachment', filename="EICAR.com")
message.attach(eicar_mime)
query['data'] = decode_email(message)
data = json.dumps(query)
response = requests.post(self.url + "query", data=data)
values = [x["values"] for x in response.json()['results']]
self.assertIn('EICAR.com', values)
for i in response.json()['results']:
if i["types"][0] == 'attachment':
self.assertEqual(i["values"], "EICAR.com")
attch_data = base64.b64decode(i["data"])
self.assertEqual(attch_data,
b'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-')
def test_email_attachment_unpack(self): def test_email_attachment_unpack(self):
raise NotImplementedError("NOT IMPLEMENTED") query = {"module":"email_import"}
with open("tests/test_attachment.eml", "r") as f: query["config"] = {"unzip_attachments": "true",
data = json.dumps({"module":"email_import", "guess_zip_attachment_passwords": None,
"data":str(base64.b64encode(bytes(f.read(), 'utf8')), "extract_urls": None}
'utf8')}).encode('utf8') message = get_base_email()
response = requests.post(self.url + "query", data=data) text = """I am a test e-mail"""
response.connection.close() message.attach(MIMEText(text, 'plain'))
print(response.json()) with open("tests/EICAR.com.zip", "rb") as fp:
eicar_mime = MIMEApplication(fp.read(), 'zip')
def test_email_attachment_as_malware(self): eicar_mime.add_header('Content-Disposition', 'attachment', filename="EICAR.com.zip")
raise NotImplementedError("NOT IMPLEMENTED") message.attach(eicar_mime)
with open("tests/test_attachment.eml", "r") as f: query['data'] = decode_email(message)
data = json.dumps({"module":"email_import", data = json.dumps(query)
"data":str(base64.b64encode(bytes(f.read(), 'utf8')), response = requests.post(self.url + "query", data=data)
'utf8')}).encode('utf8') values = [x["values"] for x in response.json()["results"]]
response = requests.post(self.url + "query", data=data) self.assertIn('EICAR.com', values)
response.connection.close() self.assertIn('EICAR.com.zip', values)
print(response.json()) for i in response.json()['results']:
if i["values"] == 'EICAR.com.zip':
def test_email_attachment_as_malware_password_in_body(self): with zipfile.ZipFile(io.BytesIO(base64.b64decode(i["data"])), 'r') as zf:
raise NotImplementedError("NOT IMPLEMENTED") with zf.open("EICAR.com") as ec:
test_email = helper_create_email({"body":"""The password is infected attch_data = ec.read()
self.assertEqual(attch_data,
Best, b'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-')
"some random malware researcher who thinks he is slick." """}) if i["values"] == 'EICAR.com':
attch_data = base64.b64decode(i["data"])
with open("tests/test_attachment.eml", "r") as f: self.assertEqual(attch_data,
data = json.dumps({"module":"email_import", b'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-')
"data":str(base64.b64encode(test_email)).encode('utf8')})
response = requests.post(self.url + "query", data=data)
response.connection.close()
print(response.json())
def test_email_attachment_as_malware_password_in_body_sentance(self):
raise NotImplementedError("NOT IMPLEMENTED")
test_email = helper_create_email({"body":"""The password is infected.
Best,
"some random malware researcher who thinks he is slick." """})
with open("tests/test_attachment.eml", "r") as f:
data = json.dumps({"module":"email_import",
"data":str(base64.b64encode(test_email)}).encode('utf8')
response = requests.post(self.url + "query", data=data)
response.connection.close()
print(response.json())
def test_email_attachment_as_malware_password_in_html_body(self):
raise NotImplementedError("NOT IMPLEMENTED")
# TODO Encrypt baseline attachment with "i like pineapples!!!"
# TODO Figure out how to set HTML body
test_email = helper_create_email({"body":"""The password is found in this email.
It is "i like pineapples!!!".
Best,
"some random malware researcher who thinks he is slick." """})
response = requests.post(self.url + "query", data=data)
response.connection.close()
print(response.json())
def test_email_attachment_as_malware_password_in_subject(self):
raise NotImplementedError("NOT IMPLEMENTED")
with open("tests/test_attachment.eml", "r") as f:
data = json.dumps({"module":"email_import",
"data":str(base64.b64encode(bytes(f.read(), 'utf8')),
'utf8')}).encode('utf8')
response = requests.post(self.url + "query", data=data)
response.connection.close()
print(response.json())
def test_email_attachment_as_malware_passphraise_in_quotes(self):
raise NotImplementedError("NOT IMPLEMENTED")
# TODO Encrypt baseline attachment with "i like pineapples!!!"
test_email = helper_create_email({"body":"""The password is found in this email.
It is "i like pineapples!!!".
Best,
"some random malware researcher who thinks he is slick." """})
with open("tests/test_attachment.eml", "r") as f:
data = json.dumps({"module":"email_import",
"data":str(base64.b64encode(test_email)}).encode('utf8')
response = requests.post(self.url + "query", data=data)
response.connection.close()
print(response.json())
def test_email_attachment_as_malware_passphraise_in_brackets(self):
raise NotImplementedError("NOT IMPLEMENTED")
# TODO Encrypt baseline attachment with "i like pineapples!!!"
test_email = helper_create_email({"body":"""The password is found in this email.
It is [i like pineapples!!!].
Best,
"some random malware researcher who thinks he is slick." """})
with open("tests/test_attachment.eml", "r") as f:
data = json.dumps({"module":"email_import",
"data":str(base64.b64encode(test_email)}).encode('utf8')
response = requests.post(self.url + "query", data=data)
response.connection.close()
print(response.json())
def test_email_attachment_unpack_and_as_malware(self):
raise NotImplementedError("NOT IMPLEMENTED")
with open("tests/test_attachment.eml", "r") as f:
data = json.dumps({"module":"email_import",
"data":str(base64.b64encode(bytes(f.read(), 'utf8')),
'utf8')}).encode('utf8')
response = requests.post(self.url + "query", data=data)
response.connection.close()
print(response.json())
def test_virustotal(self):
# This can't actually be tested without disclosing a private
# API key. This will attempt to run with a .gitignored keyfile
# and pass if it can't find one
if not os.path.exists("tests/bodyvirustotal.json"):
return
with open("tests/bodyvirustotal.json", "r") as f:
response = requests.post(self.url + "query", data=f.read()).json()
assert(response)
response.connection.close()
def helper_create_email(**conf):
raise NotImplementedError("NOT IMPLEMENTED")
attachment_name = conf.get("attachment_name", None)
subject = conf.get("subject", "Hello friend this is a test email")
subject = conf.get("subject", "Hello friend this is a test email")
received = conf.get("Received", ["""Received: via dmail-2008.19 for +INBOX;\n\tTue, 3 Feb 2009 19:29:12 -0600 (CST)""","""Received: from abc.luxsci.com ([10.10.10.10])\n\tby xyz.luxsci.com (8.13.7/8.13.7) with\n\tESMTP id n141TCa7022588\n\tfor <test@domain.com>;\n\tTue, 3 Feb 2009 19:29:12 -0600""", """Received: from [192.168.0.3] (verizon.net [44.44.44.44])\n\t(user=test@sender.com mech=PLAIN bits=2)\n\tby abc.luxsci.com (8.13.7/8.13.7) with\n\tESMTP id n141SAfo021855\n\t(version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA\n\tbits=256 verify=NOT) for <test@domain.com>;\n\tTue, 3 Feb 2009 19:28:10 -0600"""])
return_path = conf.get("Return-Path", "Return-Path: evil_spoofer@example.com")
def test_email_attachment_unpack_with_password(self):
query = {"module":"email_import"}
query["config"] = {"unzip_attachments": "true",
"guess_zip_attachment_passwords": 'true',
"extract_urls": None}
message = get_base_email()
text = """I am a test e-mail"""
message.attach(MIMEText(text, 'plain'))
with open("tests/infected.zip", "rb") as fp:
eicar_mime = MIMEApplication(fp.read(), 'zip')
eicar_mime.add_header('Content-Disposition', 'attachment', filename="EICAR.com.zip")
message.attach(eicar_mime)
query['data'] = decode_email(message)
data = json.dumps(query)
response = requests.post(self.url + "query", data=data)
values = [x["values"] for x in response.json()["results"]]
self.assertIn('EICAR.com', values)
self.assertIn('EICAR.com.zip', values)
for i in response.json()['results']:
if i["values"] == 'EICAR.com.zip':
with zipfile.ZipFile(io.BytesIO(base64.b64decode(i["data"])), 'r') as zf:
# Make sure password was set and still in place
self.assertRaises(RuntimeError, zf.open, "EICAR.com")
if i["values"] == 'EICAR.com':
attch_data = base64.b64decode(i["data"])
self.assertEqual(attch_data,
b'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-')
def test_email_attachment_password_in_body(self):
query = {"module":"email_import"}
query["config"] = {"unzip_attachments": "true",
"guess_zip_attachment_passwords": 'true',
"extract_urls": None}
message = get_base_email()
text = """I am a -> STRINGS <- test e-mail"""
message.attach(MIMEText(text, 'plain'))
with open("tests/short_password.zip", "rb") as fp:
eicar_mime = MIMEApplication(fp.read(), 'zip')
eicar_mime.add_header('Content-Disposition', 'attachment', filename="EICAR.com.zip")
message.attach(eicar_mime)
query['data'] = decode_email(message)
data = json.dumps(query)
response = requests.post(self.url + "query", data=data)
values = [x["values"] for x in response.json()["results"]]
self.assertIn('EICAR.com', values)
for i in response.json()['results']:
if i["values"] == 'EICAR.com':
attch_data = base64.b64decode(i["data"]).decode()
self.assertEqual(attch_data,
'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-')
def test_email_attachment_password_in_body_quotes(self):
query = {"module":"email_import"}
query["config"] = {"unzip_attachments": "true",
"guess_zip_attachment_passwords": 'true',
"extract_urls": None}
message = get_base_email()
text = """I am a test e-mail
the password is "a long password".
That is all.
"""
message.attach(MIMEText(text, 'plain'))
with open("tests/longer_password.zip", "rb") as fp:
eicar_mime = MIMEApplication(fp.read(), 'zip')
eicar_mime.add_header('Content-Disposition', 'attachment', filename="EICAR.com.zip")
message.attach(eicar_mime)
query['data'] = decode_email(message)
data = json.dumps(query)
response = requests.post(self.url + "query", data=data)
values = [x["values"] for x in response.json()["results"]]
self.assertIn('EICAR.com', values)
for i in response.json()['results']:
# Check that it could be extracted.
if i["values"] == 'EICAR.com':
attch_data = base64.b64decode(i["data"]).decode()
self.assertEqual(attch_data,
'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-')
def test_email_attachment_password_in_html_body(self):
query = {"module":"email_import"}
query["config"] = {"unzip_attachments": "true",
"guess_zip_attachment_passwords": 'true',
"extract_urls": None}
message = get_base_email()
text = """I am a test e-mail
the password is NOT "this string".
That is all.
"""
html = """\
<html>
<head></head>
<body>
<p>Hi!<br>
This is the real password?<br>
It is "a long password".
</p>
</body>
</html>
"""
message.attach(MIMEText(text, 'plain'))
message.attach(MIMEText(html, 'html'))
with open("tests/longer_password.zip", "rb") as fp:
eicar_mime = MIMEApplication(fp.read(), 'zip')
eicar_mime.add_header('Content-Disposition', 'attachment', filename="EICAR.com.zip")
message.attach(eicar_mime)
query['data'] = decode_email(message)
data = json.dumps(query)
response = requests.post(self.url + "query", data=data)
#print(response.json())
values = [x["values"] for x in response.json()["results"]]
self.assertIn('EICAR.com', values)
for i in response.json()['results']:
# Check that it could be extracted.
if i["values"] == 'EICAR.com':
attch_data = base64.b64decode(i["data"]).decode()
self.assertEqual(attch_data,
'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-')
def test_email_attachment_password_in_subject(self):
query = {"module":"email_import"}
query["config"] = {"unzip_attachments": "true",
"guess_zip_attachment_passwords": 'true',
"extract_urls": None}
message = get_base_email()
message.replace_header("Subject", 'I contain the -> "a long password" <- that is the password')
text = """I am a test e-mail
the password is NOT "this string".
That is all.
"""
message.attach(MIMEText(text, 'plain'))
with open("tests/longer_password.zip", "rb") as fp:
eicar_mime = MIMEApplication(fp.read(), 'zip')
eicar_mime.add_header('Content-Disposition', 'attachment', filename="EICAR.com.zip")
message.attach(eicar_mime)
query['data'] = decode_email(message)
data = json.dumps(query)
response = requests.post(self.url + "query", data=data)
values = [x["values"] for x in response.json()["results"]]
self.assertIn('EICAR.com', values)
self.assertIn('I contain the -> "a long password" <- that is the password', values)
for i in response.json()['results']:
# Check that it could be extracted.
if i["values"] == 'EICAR.com':
attch_data = base64.b64decode(i["data"]).decode()
self.assertEqual(attch_data,
'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-')
def test_email_extract_html_body_urls(self):
query = {"module":"email_import"}
query["config"] = {"unzip_attachments": None,
"guess_zip_attachment_passwords": None,
"extract_urls": "true"}
message = get_base_email()
text = """I am a test e-mail
That is all.
"""
html = """\
<html>
<head></head>
<body>
<p>Hi!<br>
<p>MISP modules are autonomous modules that can be used for expansion and other services in <a href="https://github.com/MISP/MISP">MISP</a>.</p>
<p>The modules are written in Python 3 following a simple API interface. The objective is to ease the extensions of MISP functionalities
without modifying core components. The API is available via a simple REST API which is independent from MISP installation or configuration.</p>
<p>MISP modules support is included in MISP starting from version 2.4.28.</p>
<p>For more information: <a href="https://www.circl.lu/assets/files/misp-training/3.1-MISP-modules.pdf">Extending MISP with Python modules</a> slides from MISP training.</p>
</p>
</body>
</html>
"""
message.attach(MIMEText(text, 'plain'))
message.attach(MIMEText(html, 'html'))
query['data'] = decode_email(message)
data = json.dumps(query)
response = requests.post(self.url + "query", data=data)
#print(response.json())
values = [x["values"] for x in response.json()["results"]]
self.assertIn("https://github.com/MISP/MISP", values)
self.assertIn("https://www.circl.lu/assets/files/misp-training/3.1-MISP-modules.pdf", values)
#def test_domaintools(self): #def test_domaintools(self):
# query = {'config': {'username': 'test_user', 'api_key': 'test_key'}, 'module': 'domaintools', 'domain': 'domaintools.com'} # query = {'config': {'username': 'test_user', 'api_key': 'test_key'}, 'module': 'domaintools', 'domain': 'domaintools.com'}
@ -221,6 +358,38 @@ def helper_create_email(**conf):
# response = requests.post(self.url + "query", data=json.dumps(query)).json() # response = requests.post(self.url + "query", data=json.dumps(query)).json()
# print(response) # print(response)
def decode_email(message):
message64 = base64.b64encode(message.as_bytes()).decode()
return message64
def get_base_email():
headers = {"Received":"via dmail-2008.19 for +INBOX; Tue, 3 Feb 2009 19:29:12 -0600 (CST)",
"Received":"from abc.luxsci.com ([10.10.10.10]) by xyz.luxsci.com (8.13.7/8.13.7) with ESMTP id n141TCa7022588 for <test@domain.com>; Tue, 3 Feb 2009 19:29:12 -0600",
"Received":"from [192.168.0.3] (verizon.net [44.44.44.44]) (user=test@sender.com mech=PLAIN bits=2) by abc.luxsci.com (8.13.7/8.13.7) with ESMTP id n141SAfo021855 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NOT) for <test@domain.com>; Tue, 3 Feb 2009 19:28:10 -0600",
"X-Received":"by 192.168.0.45 with SMTP id q4mr156123401yw1g.911.1912342394963; Tue, 3 Feb 2009 19:32:15 -0600 (PST)",
"Message-ID":"<4988EF2D.40804@example.com>",
"Date":"Tue, 03 Feb 2009 20:28:13 -0500",
"From":'"Innocent Person" <IgnoreMeImInnocent@sender.com>',
"User-Agent":'Thunderbird 2.0.0.19 (Windows/20081209)',
"Sender":'"Malicious MailAgent" <mailagent@example.com>',
"References":"<CI7DgL-A6dm92s7gf4-88g@E_0x238G4K2H08H9SDwsw8b6LwuA@mail.example.com>",
"In-Reply-To":"<CI7DgL-A6dm92s7gf4-88g@E_0x238G4K2H08H9SDwsw8b6LwuA@mail.example.com>",
"Accept-Language":'en-US',
"X-Mailer":'mlx 5.1.7',
"Return-Path": "evil_spoofer@example.com",
"Thread-Topic":'This is a thread.',
"Thread-Index":'AQHSR8Us3H3SoaY1oUy9AAwZfMF922bnA9GAgAAi9s4AAGvxAA==',
"Content-Language":'en-US',
"To":'"Testy Testerson" <test@domain.com>',
"Cc":'"Second Person" <second@domain.com>, "Other Friend" <other@friend.net>, "Last One" <last_one@finally.com>',
"Subject":'Example Message',
"MIME-Version":'1.0'}
msg = MIMEMultipart()
for key, val in headers.items():
msg.add_header(key, val)
return msg
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,167 +0,0 @@
Received: via dmail-2008.19 for +INBOX;
Tue, 3 Feb 2009 19:29:12 -0600 (CST)
Received: from abc.luxsci.com ([10.10.10.10])
by xyz.luxsci.com (8.13.7/8.13.7) with
ESMTP id n141TCa7022588
for <test@domain.com>;
Tue, 3 Feb 2009 19:29:12 -0600
Return-Path: evil_spoofer@example.com
Received: from [192.168.0.3] (verizon.net [44.44.44.44])
(user=test@sender.com mech=PLAIN bits=2)
by abc.luxsci.com (8.13.7/8.13.7) with
ESMTP id n141SAfo021855
(version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA
bits=256 verify=NOT) for <test@domain.com>;
Tue, 3 Feb 2009 19:28:10 -0600
Message-ID: <4988EF2D.40804@domain.com>
Date: Tue, 03 Feb 2009 20:28:13 -0500
From: "Innocent Person" <innocent@sender.com>
User-Agent: Thunderbird 2.0.0.19 (Windows/20081209)
MIME-Version: 1.0
To: "Testy Testerson" <test@domain.com>
Cc: "Second Person" <second@domain.com>, "Other Friend" <other@friend.net>, "Last One" <last_one@finally.com>
Subject: Example Message
Content-Type: multipart/mixed; boundary=047d7b2edc8d80dac9053f7a3f8d
--047d7b2edc8d80dac9053f7a3f8d
Content-Type: multipart/alternative; boundary=047d7b2edc8d80dac4053f7a3f8b
--047d7b2edc8d80dac4053f7a3f8b
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64
TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg
c2VkIGRvIGVpdXNtb2QNCnRlbXBvciBpbmNpZGlkdW50IHV0IGxhYm9yZSBldCBkb2xvcmUgbWFn
bmEgYWxpcXVhLiBVdCBlbmltIGFkIG1pbmltDQp2ZW5pYW0sIHF1aXMgbm9zdHJ1ZCBleGVyY2l0
YXRpb24gdWxsYW1jbyBsYWJvcmlzIG5pc2kgdXQgYWxpcXVpcCBleCBlYQ0KY29tbW9kbyBjb25z
ZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0
ZQ0KdmVsaXQgZXNzZSBjaWxsdW0gZG9sb3JlIGV1IGZ1Z2lhdCBudWxsYSBwYXJpYXR1ci4gRXhj
ZXB0ZXVyIHNpbnQgb2NjYWVjYXQNCmN1cGlkYXRhdCBub24gcHJvaWRlbnQsIHN1bnQgaW4gY3Vs
cGEgcXVpIG9mZmljaWEgZGVzZXJ1bnQgbW9sbGl0IGFuaW0gaWQNCmVzdCBsYWJvcnVtLg0KDQrQ
ndCw0Lwg0LvRjNCw0LHQvtGA0Y0g0L/QvtGI0LbQuNC8INC10LQsINC80Y3Qu9GMINC90L4g0L7Q
sdC70YzQudC60LLRjtGNINGN0YDRgNC+0YDQuNCx0YPQtyDQsNCx0YXQvtGA0YDRjdCw0L3Rgiwg
0LDRgiDQu9Cw0LHQvtGA0LDQvNGO0LcNCtCy0Y7Qu9GM0L/Rg9GC0LDRgtGLINCy0Y3Quy4g0JnQ
vSDRg9C90Y7QvCDRjtGA0LHQsNC90LnRgtCw0LYg0LLQtdC60LYsINC50L0g0Y3QvtC2INGB0YrR
jtC80LzQviDQtNC10LrRgtCw0LYuINCQ0LvQuNGRINC80Y7QvdGL0YDRjQ0K0ZHRg9C00ZHQutCw
0LHQtdGCINC90YvQuiDRjdGOLCDRg9GCINC30LDQu9GM0Ysg0L/QvtGA0YDQviDQtNC50LrQuNGC
INCy0LjQvC4g0J3QviDQv9C+0L3QtNGN0YDRjtC8INC30LrRgNC40L/RgtC+0YDRjdC8INGL0LDQ
vC4NCg0K54mh5pyI5YWD5L2P6YCj5Yud6KuW5pyd56Wd5aSJ6ZmN5b6X44CC5Lq65q2j6IGe5LqL
6KaW57aZ55S755m65p2l5Yui5bee5ZaE5pyA6JGJ6ICF55u444CC55+l6KW/5Zu95pKy55Sf5riI
5YWD5YCN56aP5Zuz57SE5pyI5YiG546L44CC5aWz5ryU6KaL5Y2U5rK75Yqg6K2w5b+F6YKj6KiY
6aOy5LiN5Z6L6KeS5rOo6YCy5q6L5LiW44CCDQroppbmlq3pn7PntLDooZfov5Hlkb3mlq3mpJzl
p7/nlJ/lhYXmsr/lpKfmsZDku67liIDjgILokZfoirjms5XmnaXploDlhYjlsJHlt53mtojnqJrn
ooHogZ7lrrnnrKznmYLmuKzlsI/nlbPokYnjgILmlZnmpJznkIPmraLmjZzluLjoq77npoHlspDk
u5Xph5HovInlkajmvZ/lhKrjgILnlLvoqq3otorooYDmpa3plbflgaXmj5DlsZ7pg6jkv53kuIfl
vqnkuIfnj77muIvoqKrlrq7lrrnov5HjgIINCuaYjuW/heWbsumDteaBteW6g+acgOa0l+Wunei8
iei/lOmDqOOAgg0KDQrgpLngpYvgpJfgpL4g4KS44KSC4KSq4KS+4KSm4KSVIOCkheCkqOClgeCk
leClguCksiDgpLjgpL7gpLDgpY3gpLXgpJzgpKjgpL/gpJUg4KS14KS/4KSt4KS+4KSXIOCkhuCk
nOCkquCksCDgpLjgpYHgpJrgpKjgpL4g4KS44KWN4KSl4KS/4KSk4KS/IOCkteCkvuCksOCljeCk
pOCkvuCksuCkvuCkqiDgpKrgpYHgpLfgpY3gpJ/gpL/gpJXgpLDgpY3gpKTgpL4NCuCkruClgeCk
luCljeCkr+CkpOCkuSDgpLXgpL7gpLDgpY3gpKTgpL7gpLLgpL7gpKog4KSq4KWN4KSw4KWL4KSk
4KWN4KS44KS+4KS54KS/4KSkIOCkieCkuOCkleClhyDgpLjgpK7gpL7gpJzgpYsg4KWt4KS54KSy
IOCknOCkv+CkruCljeCkruClhyDgpJTgpLDgpY3gpargpavgpaYg4KSm4KS44KWN4KSk4KS+4KS1
4KWH4KScIOCkueCkruCkvuCksOClgA0K4KSc4KS/4KS44KSV4KWAIOCkuOCkruCkvuCknCDgpKzg
pL/gpKjgpY3gpKbgpYHgpJMg4KS44KWL4KWe4KWN4KSf4KS14KWH4KSwIOCkteCljeCkr+CkvuCk
luCljeCkr+CkvuCkqCDgpK7gpYfgpILgpK3gpJ/gpYMg4KS14KS+4KS44KWN4KSk4KS1IOCkquCl
jeCksOClh+CksOCkqOCkviDgpLjgpYDgpK7gpL/gpKQg4KSc4KWI4KS44KWHIOCkquCkueCli+Ck
mg0K4KSo4KSv4KWH4KSy4KS/4KSPIOCkueCliOClpOCkheCkreClgCDgpLjgpK3gpL/gpLjgpK7g
pJwg4KS14KS/4KS14KSw4KSjIOCkluCksOCkv+CkpuCkqOClhyDgpKjgpL/gpLDgpY3gpKbgpYfg
pLYg4KS14KWN4KSv4KS14KS54KS+4KSwIOCkreCkvuCkpOCkvyDgpLXgpL/gpLbgpY3gpLUg4KS5
4KWA4KSV4KSuIOCknOCkvuCkqOCkpOClhw0K4KSJ4KSm4KWN4KSv4KWL4KSXIOCkquCkpOCljeCk
sOCkv+CkleCkviDgpLXgpY3gpLDgpYHgpKbgpY3gpKfgpL8g4KS54KS+4KSw4KWN4KSh4KS14KWH
4KSwIOCkheCkqOCljeCkpOCksOCksOCkvuCkt+CljeCkn+CljeCksOClgOCkr+CkleCksOCkqCDg
pKfgpY3gpLXgpKjgpL8g4KSP4KS14KSu4KWNIOCkpuCljeCkteCkvuCksOCkviDgpI7gpLjgpL7g
pJzgpYDgpLgNCuCkquClgeCkt+CljeCkn+Ckv+CkleCksOCljeCkpOCkviDgpLXgpL/gpLbgpY3g
pLUg4KSw4KSa4KSo4KS+DQoNCtmIINit2YrYqyDZgtix2LHYqiDZh9in2LHYqNixINin2YTZhtiy
2KfYuSwg2LPYp9i52Kkg2KfZhNmH2KfYr9mKINil2LAg2YjZgdmKLCDYudmGINmF2YXYpyDZiNiy
2KfYsdipINmI2YfZiNmE2YbYr9in2IwuINil2LANCtin2YTYo9mI2YQg2KjZhdio2KfYsdmD2Kkg
2YTZhdmRLiDYqNit2Ksg2YrYt9mI2YQg2YjYp9mE2YXYudiv2KfYqiDZo9mgLCDZgdmKINmF2KfZ
itmIINmE2YTYrNiy2LEg2YjYs9mF2ZHZitiqINmB2YLYry4g2YXYpw0K2YHYsdmG2LPZitipINis
2LLZitix2KrZiiDYp9mE2KvYp9mE2Ksg2YjZhdmGLiDZhdmD2YYg2YfZiCDZhNmD2YjZhiDZhdiv
2YrZhtipINmI2KjYsdmK2LfYp9mG2YrYpy4g2aPZoCDZiNmE2YUg2KfZhNmE2Ycg2KfZhNmF2KrY
rdiv2KkuDQrYqtmE2YMg2YjYqtix2YMg2YTYqNmI2YTZhtiv2KfYjCDZgtivLCDZh9iw2Kcg2YjY
rNmH2KfZhiDYp9mE2K7Yp9i32YHYqSDYp9mE2YjYstix2KfYoSDYudmGLg0KDQpCZXN0LA0KWW91
ciBGcmllbmQNCg==
--047d7b2edc8d80dac4053f7a3f8b
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: base64
PGRpdiBkaXI9Imx0ciI+PGRpdiBzdHlsZT0iZm9udC1zaXplOjEyLjhweCI+TG9yZW0gaXBzdW0g
ZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNt
b2Q8L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTIuOHB4Ij50ZW1wb3IgaW5jaWRpZHVudCB1
dCBsYWJvcmUgZXQgZG9sb3JlIG1hZ25hIGFsaXF1YS4gVXQgZW5pbSBhZCBtaW5pbTwvZGl2Pjxk
aXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44cHgiPnZlbmlhbSwgcXVpcyBub3N0cnVkIGV4ZXJjaXRh
dGlvbiB1bGxhbWNvIGxhYm9yaXMgbmlzaSB1dCBhbGlxdWlwIGV4IGVhPC9kaXY+PGRpdiBzdHls
ZT0iZm9udC1zaXplOjEyLjhweCI+Y29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBk
b2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZTwvZGl2PjxkaXYgc3R5bGU9ImZvbnQt
c2l6ZToxMi44cHgiPnZlbGl0IGVzc2UgY2lsbHVtIGRvbG9yZSBldSBmdWdpYXQgbnVsbGEgcGFy
aWF0dXIuIEV4Y2VwdGV1ciBzaW50IG9jY2FlY2F0PC9kaXY+PGRpdiBzdHlsZT0iZm9udC1zaXpl
OjEyLjhweCI+Y3VwaWRhdGF0IG5vbiBwcm9pZGVudCwgc3VudCBpbiBjdWxwYSBxdWkgb2ZmaWNp
YSBkZXNlcnVudCBtb2xsaXQgYW5pbSBpZDwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44
cHgiPmVzdCBsYWJvcnVtLjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44cHgiPjxicj48
L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTIuOHB4Ij7QndCw0Lwg0LvRjNCw0LHQvtGA0Y0g
0L/QvtGI0LbQuNC8INC10LQsINC80Y3Qu9GMINC90L4g0L7QsdC70YzQudC60LLRjtGNINGN0YDR
gNC+0YDQuNCx0YPQtyDQsNCx0YXQvtGA0YDRjdCw0L3Rgiwg0LDRgiDQu9Cw0LHQvtGA0LDQvNGO
0Lc8L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTIuOHB4Ij7QstGO0LvRjNC/0YPRgtCw0YLR
iyDQstGN0LsuINCZ0L0g0YPQvdGO0Lwg0Y7RgNCx0LDQvdC50YLQsNC2INCy0LXQutC2LCDQudC9
INGN0L7QtiDRgdGK0Y7QvNC80L4g0LTQtdC60YLQsNC2LiDQkNC70LjRkSDQvNGO0L3Ri9GA0Y08
L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTIuOHB4Ij7RkdGD0LTRkdC60LDQsdC10YIg0L3R
i9C6INGN0Y4sINGD0YIg0LfQsNC70YzRiyDQv9C+0YDRgNC+INC00LnQutC40YIg0LLQuNC8LiDQ
ndC+INC/0L7QvdC00Y3RgNGO0Lwg0LfQutGA0LjQv9GC0L7RgNGN0Lwg0YvQsNC8LjwvZGl2Pjxk
aXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44cHgiPjxicj48L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNp
emU6MTIuOHB4Ij7niaHmnIjlhYPkvY/pgKPli53oq5bmnJ3npZ3lpInpmY3lvpfjgILkurrmraPo
gZ7kuovoppbntpnnlLvnmbrmnaXli6Llt57lloTmnIDokYnogIXnm7jjgII8d2JyPuefpeilv+Wb
veaSsueUn+a4iOWFg+WAjeemj+Wbs+e0hOaciOWIhueOi+OAgjx3YnI+5aWz5ryU6KaL5Y2U5rK7
5Yqg6K2w5b+F6YKj6KiY6aOy5LiN5Z6L6KeS5rOo6YCy5q6L5LiW44CCPHdicj7oppbmlq3pn7Pn
tLDooZfov5Hlkb3mlq3mpJzlp7/nlJ/lhYXmsr/lpKfmsZDku67liIDjgII8d2JyPuiRl+iKuOaz
leadpemWgOWFiOWwkeW3nea2iOeomueigeiBnuWuueesrOeZgua4rOWwj+eVs+iRieOAgjx3YnI+
5pWZ5qSc55CD5q2i5o2c5bi46Ku+56aB5bKQ5LuV6YeR6LyJ5ZGo5r2f5YSq44CCPHdicj7nlLvo
qq3otorooYDmpa3plbflgaXmj5DlsZ7pg6jkv53kuIflvqnkuIfnj77muIvoqKrlrq7lrrnov5Hj
gII8d2JyPuaYjuW/heWbsumDteaBteW6g+acgOa0l+Wunei8iei/lOmDqOOAgjwvZGl2PjxkaXYg
c3R5bGU9ImZvbnQtc2l6ZToxMi44cHgiPjxicj48L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6
MTIuOHB4Ij7gpLngpYvgpJfgpL4g4KS44KSC4KSq4KS+4KSm4KSVIOCkheCkqOClgeCkleClguCk
siDgpLjgpL7gpLDgpY3gpLXgpJzgpKjgpL/gpJUg4KS14KS/4KSt4KS+4KSXIOCkhuCknOCkquCk
sCDgpLjgpYHgpJrgpKjgpL4g4KS44KWN4KSl4KS/4KSk4KS/IOCkteCkvuCksOCljeCkpOCkvuCk
suCkvuCkqiDgpKrgpYHgpLfgpY3gpJ/gpL/gpJXgpLDgpY3gpKTgpL48L2Rpdj48ZGl2IHN0eWxl
PSJmb250LXNpemU6MTIuOHB4Ij7gpK7gpYHgpJbgpY3gpK/gpKTgpLkg4KS14KS+4KSw4KWN4KSk
4KS+4KSy4KS+4KSqIOCkquCljeCksOCli+CkpOCljeCkuOCkvuCkueCkv+CkpCDgpIngpLjgpJXg
pYcg4KS44KSu4KS+4KSc4KWLIOClreCkueCksiDgpJzgpL/gpK7gpY3gpK7gpYcg4KSU4KSw4KWN
4KWq4KWr4KWmIOCkpuCkuOCljeCkpOCkvuCkteClh+CknCDgpLngpK7gpL7gpLDgpYA8L2Rpdj48
ZGl2IHN0eWxlPSJmb250LXNpemU6MTIuOHB4Ij7gpJzgpL/gpLjgpJXgpYAg4KS44KSu4KS+4KSc
IOCkrOCkv+CkqOCljeCkpuClgeCkkyDgpLjgpYvgpZ7gpY3gpJ/gpLXgpYfgpLAg4KS14KWN4KSv
4KS+4KSW4KWN4KSv4KS+4KSoIOCkruClh+CkguCkreCkn+ClgyDgpLXgpL7gpLjgpY3gpKTgpLUg
4KSq4KWN4KSw4KWH4KSw4KSo4KS+IOCkuOClgOCkruCkv+CkpCDgpJzgpYjgpLjgpYcg4KSq4KS5
4KWL4KSaPC9kaXY+PGRpdiBzdHlsZT0iZm9udC1zaXplOjEyLjhweCI+4KSo4KSv4KWH4KSy4KS/
4KSPIOCkueCliOClpOCkheCkreClgCDgpLjgpK3gpL/gpLjgpK7gpJwg4KS14KS/4KS14KSw4KSj
IOCkluCksOCkv+CkpuCkqOClhyDgpKjgpL/gpLDgpY3gpKbgpYfgpLYg4KS14KWN4KSv4KS14KS5
4KS+4KSwIOCkreCkvuCkpOCkvyDgpLXgpL/gpLbgpY3gpLUg4KS54KWA4KSV4KSuIOCknOCkvuCk
qOCkpOClhzwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44cHgiPuCkieCkpuCljeCkr+Cl
i+CklyDgpKrgpKTgpY3gpLDgpL/gpJXgpL4g4KS14KWN4KSw4KWB4KSm4KWN4KSn4KS/IOCkueCk
vuCksOCljeCkoeCkteClh+CksCDgpIXgpKjgpY3gpKTgpLDgpLDgpL7gpLfgpY3gpJ/gpY3gpLDg
pYDgpK/gpJXgpLDgpKgg4KSn4KWN4KS14KSo4KS/IOCkj+CkteCkruCljSDgpKbgpY3gpLXgpL7g
pLDgpL4g4KSO4KS44KS+4KSc4KWA4KS4PC9kaXY+PGRpdiBzdHlsZT0iZm9udC1zaXplOjEyLjhw
eCI+4KSq4KWB4KS34KWN4KSf4KS/4KSV4KSw4KWN4KSk4KS+IOCkteCkv+CktuCljeCktSDgpLDg
pJrgpKjgpL48L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTIuOHB4Ij48YnI+PC9kaXY+PGRp
diBzdHlsZT0iZm9udC1zaXplOjEyLjhweCI+2Ygg2K3ZitirINmC2LHYsdiqINmH2KfYsdio2LEg
2KfZhNmG2LLYp9i5LCDYs9in2LnYqSDYp9mE2YfYp9iv2Yog2KXYsCDZiNmB2YosINi52YYg2YXZ
hdinINmI2LLYp9ix2Kkg2YjZh9mI2YTZhtiv2KfYjC4g2KXYsDwvZGl2PjxkaXYgc3R5bGU9ImZv
bnQtc2l6ZToxMi44cHgiPtin2YTYo9mI2YQg2KjZhdio2KfYsdmD2Kkg2YTZhdmRLiDYqNit2Ksg
2YrYt9mI2YQg2YjYp9mE2YXYudiv2KfYqiDZo9mgLCDZgdmKINmF2KfZitmIINmE2YTYrNiy2LEg
2YjYs9mF2ZHZitiqINmB2YLYry4g2YXYpzwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44
cHgiPtmB2LHZhtiz2YrYqSDYrNiy2YrYsdiq2Yog2KfZhNir2KfZhNirINmI2YXZhi4g2YXZg9mG
INmH2Ygg2YTZg9mI2YYg2YXYr9mK2YbYqSDZiNio2LHZiti32KfZhtmK2KcuINmj2aAg2YjZhNmF
INin2YTZhNmHINin2YTZhdiq2K3Yr9ipLjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44
cHgiPtiq2YTZgyDZiNiq2LHZgyDZhNio2YjZhNmG2K/Yp9iMINmC2K8sINmH2LDYpyDZiNis2YfY
p9mGINin2YTYrtin2LfZgdipINin2YTZiNiy2LHYp9ihINi52YYuPC9kaXY+PGRpdiBzdHlsZT0i
Zm9udC1zaXplOjEyLjhweCI+PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMi44cHgi
PkJlc3QsPC9kaXY+PGRpdiBzdHlsZT0iZm9udC1zaXplOjEyLjhweCI+WW91ciBGcmllbmQ8L2Rp
dj4NCjwvZGl2Pg0K
--047d7b2edc8d80dac4053f7a3f8b--
--047d7b2edc8d80dac9053f7a3f8d
Content-Type: application/zip; name="file.zip"
Content-Disposition: attachment; filename="file.zip"
Content-Transfer-Encoding: base64
X-Attachment-Id: f_iulodo3k0
--047d7b2edc8d80dac9053f7a3f8d--

View File

@ -1,144 +0,0 @@
Received: via dmail-2008.19 for +INBOX;
Tue, 3 Feb 2009 19:29:12 -0600 (CST)
Received: from abc.luxsci.com ([10.10.10.10])
by xyz.luxsci.com (8.13.7/8.13.7) with
ESMTP id n141TCa7022588
for <test@domain.com>;
Tue, 3 Feb 2009 19:29:12 -0600
Return-Path: evil_spoofer@example.com
Received: from [192.168.0.3] (verizon.net [44.44.44.44])
(user=test@sender.com mech=PLAIN bits=2)
by abc.luxsci.com (8.13.7/8.13.7) with
ESMTP id n141SAfo021855
(version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA
bits=256 verify=NOT) for <test@domain.com>;
Tue, 3 Feb 2009 19:28:10 -0600
Message-ID: <4988EF2D.40804@domain.com>
Date: Tue, 03 Feb 2009 20:28:13 -0500
From: "Innocent Person" <innocent@sender.com>
User-Agent: Thunderbird 2.0.0.19 (Windows/20081209)
MIME-Version: 1.0
To: "Testy Testerson" <test@domain.com>
Cc: "Second Person" <second@domain.com>, "Other Friend" <other@friend.net>, "Last One" <last_one@finally.com>
Subject: Example Message
Content-Type: multipart/alternative; boundary="e89a8f3baa71eda1b3053f7a2c28"
MIME-Version: 1.0
--e89a8f3baa71eda1b3053f7a2c28
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64
TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg
c2VkIGRvIGVpdXNtb2QNCnRlbXBvciBpbmNpZGlkdW50IHV0IGxhYm9yZSBldCBkb2xvcmUgbWFn
bmEgYWxpcXVhLiBVdCBlbmltIGFkIG1pbmltDQp2ZW5pYW0sIHF1aXMgbm9zdHJ1ZCBleGVyY2l0
YXRpb24gdWxsYW1jbyBsYWJvcmlzIG5pc2kgdXQgYWxpcXVpcCBleCBlYQ0KY29tbW9kbyBjb25z
ZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0
ZQ0KdmVsaXQgZXNzZSBjaWxsdW0gZG9sb3JlIGV1IGZ1Z2lhdCBudWxsYSBwYXJpYXR1ci4gRXhj
ZXB0ZXVyIHNpbnQgb2NjYWVjYXQNCmN1cGlkYXRhdCBub24gcHJvaWRlbnQsIHN1bnQgaW4gY3Vs
cGEgcXVpIG9mZmljaWEgZGVzZXJ1bnQgbW9sbGl0IGFuaW0gaWQNCmVzdCBsYWJvcnVtLg0KDQrQ
ndCw0Lwg0LvRjNCw0LHQvtGA0Y0g0L/QvtGI0LbQuNC8INC10LQsINC80Y3Qu9GMINC90L4g0L7Q
sdC70YzQudC60LLRjtGNINGN0YDRgNC+0YDQuNCx0YPQtyDQsNCx0YXQvtGA0YDRjdCw0L3Rgiwg
0LDRgiDQu9Cw0LHQvtGA0LDQvNGO0LcNCtCy0Y7Qu9GM0L/Rg9GC0LDRgtGLINCy0Y3Quy4g0JnQ
vSDRg9C90Y7QvCDRjtGA0LHQsNC90LnRgtCw0LYg0LLQtdC60LYsINC50L0g0Y3QvtC2INGB0YrR
jtC80LzQviDQtNC10LrRgtCw0LYuINCQ0LvQuNGRINC80Y7QvdGL0YDRjQ0K0ZHRg9C00ZHQutCw
0LHQtdGCINC90YvQuiDRjdGOLCDRg9GCINC30LDQu9GM0Ysg0L/QvtGA0YDQviDQtNC50LrQuNGC
INCy0LjQvC4g0J3QviDQv9C+0L3QtNGN0YDRjtC8INC30LrRgNC40L/RgtC+0YDRjdC8INGL0LDQ
vC4NCg0K54mh5pyI5YWD5L2P6YCj5Yud6KuW5pyd56Wd5aSJ6ZmN5b6X44CC5Lq65q2j6IGe5LqL
6KaW57aZ55S755m65p2l5Yui5bee5ZaE5pyA6JGJ6ICF55u444CC55+l6KW/5Zu95pKy55Sf5riI
5YWD5YCN56aP5Zuz57SE5pyI5YiG546L44CC5aWz5ryU6KaL5Y2U5rK75Yqg6K2w5b+F6YKj6KiY
6aOy5LiN5Z6L6KeS5rOo6YCy5q6L5LiW44CC6KaW5pat6Z+z57Sw6KGX6L+R5ZG95pat5qSc5ae/
55Sf5YWF5rK/5aSn5rGQ5Luu5YiA44CC6JGX6Iq45rOV5p2l6ZaA5YWI5bCR5bed5raI56ia56KB
6IGe5a6556ys55mC5ris5bCP55Wz6JGJ44CC5pWZ5qSc55CD5q2i5o2c5bi46Ku+56aB5bKQ5LuV
6YeR6LyJ5ZGo5r2f5YSq44CC55S76Kqt6LaK6KGA5qWt6ZW35YGl5o+Q5bGe6YOo5L+d5LiH5b6p
5LiH54++5riL6Kiq5a6u5a656L+R44CC5piO5b+F5Zuy6YO15oG15bqD5pyA5rSX5a6d6LyJ6L+U
6YOo44CCDQoNCuCkueCli+Ckl+CkviDgpLjgpILgpKrgpL7gpKbgpJUg4KSF4KSo4KWB4KSV4KWC
4KSyIOCkuOCkvuCksOCljeCkteCknOCkqOCkv+CklSDgpLXgpL/gpK3gpL7gpJcg4KSG4KSc4KSq
4KSwIOCkuOClgeCkmuCkqOCkviDgpLjgpY3gpKXgpL/gpKTgpL8g4KS14KS+4KSw4KWN4KSk4KS+
4KSy4KS+4KSqIOCkquClgeCkt+CljeCkn+Ckv+CkleCksOCljeCkpOCkvg0K4KSu4KWB4KSW4KWN
4KSv4KSk4KS5IOCkteCkvuCksOCljeCkpOCkvuCksuCkvuCkqiDgpKrgpY3gpLDgpYvgpKTgpY3g
pLjgpL7gpLngpL/gpKQg4KSJ4KS44KSV4KWHIOCkuOCkruCkvuCknOCliyDgpa3gpLngpLIg4KSc
4KS/4KSu4KWN4KSu4KWHIOCklOCksOCljeClquClq+ClpiDgpKbgpLjgpY3gpKTgpL7gpLXgpYfg
pJwg4KS54KSu4KS+4KSw4KWADQrgpJzgpL/gpLjgpJXgpYAg4KS44KSu4KS+4KScIOCkrOCkv+Ck
qOCljeCkpuClgeCkkyDgpLjgpYvgpZ7gpY3gpJ/gpLXgpYfgpLAg4KS14KWN4KSv4KS+4KSW4KWN
4KSv4KS+4KSoIOCkruClh+CkguCkreCkn+ClgyDgpLXgpL7gpLjgpY3gpKTgpLUg4KSq4KWN4KSw
4KWH4KSw4KSo4KS+IOCkuOClgOCkruCkv+CkpCDgpJzgpYjgpLjgpYcg4KSq4KS54KWL4KSaDQrg
pKjgpK/gpYfgpLLgpL/gpI8g4KS54KWI4KWk4KSF4KSt4KWAIOCkuOCkreCkv+CkuOCkruCknCDg
pLXgpL/gpLXgpLDgpKMg4KSW4KSw4KS/4KSm4KSo4KWHIOCkqOCkv+CksOCljeCkpuClh+CktiDg
pLXgpY3gpK/gpLXgpLngpL7gpLAg4KSt4KS+4KSk4KS/IOCkteCkv+CktuCljeCktSDgpLngpYDg
pJXgpK4g4KSc4KS+4KSo4KSk4KWHDQrgpIngpKbgpY3gpK/gpYvgpJcg4KSq4KSk4KWN4KSw4KS/
4KSV4KS+IOCkteCljeCksOClgeCkpuCljeCkp+CkvyDgpLngpL7gpLDgpY3gpKHgpLXgpYfgpLAg
4KSF4KSo4KWN4KSk4KSw4KSw4KS+4KS34KWN4KSf4KWN4KSw4KWA4KSv4KSV4KSw4KSoIOCkp+Cl
jeCkteCkqOCkvyDgpI/gpLXgpK7gpY0g4KSm4KWN4KS14KS+4KSw4KS+IOCkjuCkuOCkvuCknOCl
gOCkuA0K4KSq4KWB4KS34KWN4KSf4KS/4KSV4KSw4KWN4KSk4KS+IOCkteCkv+CktuCljeCktSDg
pLDgpJrgpKjgpL4NCg0K2Ygg2K3ZitirINmC2LHYsdiqINmH2KfYsdio2LEg2KfZhNmG2LLYp9i5
LCDYs9in2LnYqSDYp9mE2YfYp9iv2Yog2KXYsCDZiNmB2YosINi52YYg2YXZhdinINmI2LLYp9ix
2Kkg2YjZh9mI2YTZhtiv2KfYjC4g2KXYsA0K2KfZhNij2YjZhCDYqNmF2KjYp9ix2YPYqSDZhNmF
2ZEuINio2K3YqyDZiti32YjZhCDZiNin2YTZhdi52K/Yp9iqINmj2aAsINmB2Yog2YXYp9mK2Ygg
2YTZhNis2LLYsSDZiNiz2YXZkdmK2Kog2YHZgtivLiDZhdinDQrZgdix2YbYs9mK2Kkg2KzYstmK
2LHYqtmKINin2YTYq9in2YTYqyDZiNmF2YYuINmF2YPZhiDZh9mIINmE2YPZiNmGINmF2K/ZitmG
2Kkg2YjYqNix2YrYt9in2YbZitinLiDZo9mgINmI2YTZhSDYp9mE2YTZhyDYp9mE2YXYqtit2K/Y
qS4NCtiq2YTZgyDZiNiq2LHZgyDZhNio2YjZhNmG2K/Yp9iMINmC2K8sINmH2LDYpyDZiNis2YfY
p9mGINin2YTYrtin2LfZgdipINin2YTZiNiy2LHYp9ihINi52YYuDQoNCkJlc3QsDQpZb3VyIEZy
aWVuZA0K
--e89a8f3baa71eda1b3053f7a2c28
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: base64
PGRpdiBkaXI9Imx0ciI+PGRpdj5Mb3JlbSBpcHN1bSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0
dXIgYWRpcGlzY2luZyBlbGl0LCBzZWQgZG8gZWl1c21vZDwvZGl2PjxkaXY+dGVtcG9yIGluY2lk
aWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW08
L2Rpdj48ZGl2PnZlbmlhbSwgcXVpcyBub3N0cnVkIGV4ZXJjaXRhdGlvbiB1bGxhbWNvIGxhYm9y
aXMgbmlzaSB1dCBhbGlxdWlwIGV4IGVhPC9kaXY+PGRpdj5jb21tb2RvIGNvbnNlcXVhdC4gRHVp
cyBhdXRlIGlydXJlIGRvbG9yIGluIHJlcHJlaGVuZGVyaXQgaW4gdm9sdXB0YXRlPC9kaXY+PGRp
dj52ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNl
cHRldXIgc2ludCBvY2NhZWNhdDwvZGl2PjxkaXY+Y3VwaWRhdGF0IG5vbiBwcm9pZGVudCwgc3Vu
dCBpbiBjdWxwYSBxdWkgb2ZmaWNpYSBkZXNlcnVudCBtb2xsaXQgYW5pbSBpZDwvZGl2PjxkaXY+
ZXN0IGxhYm9ydW0uPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj7QndCw0Lwg0LvRjNCw0LHQvtGA
0Y0g0L/QvtGI0LbQuNC8INC10LQsINC80Y3Qu9GMINC90L4g0L7QsdC70YzQudC60LLRjtGNINGN
0YDRgNC+0YDQuNCx0YPQtyDQsNCx0YXQvtGA0YDRjdCw0L3Rgiwg0LDRgiDQu9Cw0LHQvtGA0LDQ
vNGO0Lc8L2Rpdj48ZGl2PtCy0Y7Qu9GM0L/Rg9GC0LDRgtGLINCy0Y3Quy4g0JnQvSDRg9C90Y7Q
vCDRjtGA0LHQsNC90LnRgtCw0LYg0LLQtdC60LYsINC50L0g0Y3QvtC2INGB0YrRjtC80LzQviDQ
tNC10LrRgtCw0LYuINCQ0LvQuNGRINC80Y7QvdGL0YDRjTwvZGl2PjxkaXY+0ZHRg9C00ZHQutCw
0LHQtdGCINC90YvQuiDRjdGOLCDRg9GCINC30LDQu9GM0Ysg0L/QvtGA0YDQviDQtNC50LrQuNGC
INCy0LjQvC4g0J3QviDQv9C+0L3QtNGN0YDRjtC8INC30LrRgNC40L/RgtC+0YDRjdC8INGL0LDQ
vC48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PueJoeaciOWFg+S9j+mAo+WLneirluacneelneWk
iemZjeW+l+OAguS6uuato+iBnuS6i+imlue2meeUu+eZuuadpeWLouW3nuWWhOacgOiRieiAheeb
uOOAguefpeilv+WbveaSsueUn+a4iOWFg+WAjeemj+Wbs+e0hOaciOWIhueOi+OAguWls+a8lOim
i+WNlOayu+WKoOitsOW/hemCo+iomOmjsuS4jeWei+inkuazqOmAsuaui+S4luOAguimluaWremf
s+e0sOihl+i/keWRveaWreaknOWnv+eUn+WFheayv+Wkp+axkOS7ruWIgOOAguiRl+iKuOazlead
pemWgOWFiOWwkeW3nea2iOeomueigeiBnuWuueesrOeZgua4rOWwj+eVs+iRieOAguaVmeaknOeQ
g+atouaNnOW4uOirvuemgeWykOS7lemHkei8ieWRqOa9n+WEquOAgueUu+iqrei2iuihgOalremV
t+WBpeaPkOWxnumDqOS/neS4h+W+qeS4h+ePvua4i+ioquWuruWuuei/keOAguaYjuW/heWbsumD
teaBteW6g+acgOa0l+Wunei8iei/lOmDqOOAgjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+4KS5
4KWL4KSX4KS+IOCkuOCkguCkquCkvuCkpuCklSDgpIXgpKjgpYHgpJXgpYLgpLIg4KS44KS+4KSw
4KWN4KS14KSc4KSo4KS/4KSVIOCkteCkv+CkreCkvuCklyDgpIbgpJzgpKrgpLAg4KS44KWB4KSa
4KSo4KS+IOCkuOCljeCkpeCkv+CkpOCkvyDgpLXgpL7gpLDgpY3gpKTgpL7gpLLgpL7gpKog4KSq
4KWB4KS34KWN4KSf4KS/4KSV4KSw4KWN4KSk4KS+PC9kaXY+PGRpdj7gpK7gpYHgpJbgpY3gpK/g
pKTgpLkg4KS14KS+4KSw4KWN4KSk4KS+4KSy4KS+4KSqIOCkquCljeCksOCli+CkpOCljeCkuOCk
vuCkueCkv+CkpCDgpIngpLjgpJXgpYcg4KS44KSu4KS+4KSc4KWLIOClreCkueCksiDgpJzgpL/g
pK7gpY3gpK7gpYcg4KSU4KSw4KWN4KWq4KWr4KWmIOCkpuCkuOCljeCkpOCkvuCkteClh+CknCDg
pLngpK7gpL7gpLDgpYA8L2Rpdj48ZGl2PuCknOCkv+CkuOCkleClgCDgpLjgpK7gpL7gpJwg4KSs
4KS/4KSo4KWN4KSm4KWB4KSTIOCkuOCli+ClnuCljeCkn+CkteClh+CksCDgpLXgpY3gpK/gpL7g
pJbgpY3gpK/gpL7gpKgg4KSu4KWH4KSC4KSt4KSf4KWDIOCkteCkvuCkuOCljeCkpOCktSDgpKrg
pY3gpLDgpYfgpLDgpKjgpL4g4KS44KWA4KSu4KS/4KSkIOCknOCliOCkuOClhyDgpKrgpLngpYvg
pJo8L2Rpdj48ZGl2PuCkqOCkr+Clh+CksuCkv+CkjyDgpLngpYjgpaTgpIXgpK3gpYAg4KS44KSt
4KS/4KS44KSu4KScIOCkteCkv+CkteCksOCkoyDgpJbgpLDgpL/gpKbgpKjgpYcg4KSo4KS/4KSw
4KWN4KSm4KWH4KS2IOCkteCljeCkr+CkteCkueCkvuCksCDgpK3gpL7gpKTgpL8g4KS14KS/4KS2
4KWN4KS1IOCkueClgOCkleCkriDgpJzgpL7gpKjgpKTgpYc8L2Rpdj48ZGl2PuCkieCkpuCljeCk
r+Cli+CklyDgpKrgpKTgpY3gpLDgpL/gpJXgpL4g4KS14KWN4KSw4KWB4KSm4KWN4KSn4KS/IOCk
ueCkvuCksOCljeCkoeCkteClh+CksCDgpIXgpKjgpY3gpKTgpLDgpLDgpL7gpLfgpY3gpJ/gpY3g
pLDgpYDgpK/gpJXgpLDgpKgg4KSn4KWN4KS14KSo4KS/IOCkj+CkteCkruCljSDgpKbgpY3gpLXg
pL7gpLDgpL4g4KSO4KS44KS+4KSc4KWA4KS4PC9kaXY+PGRpdj7gpKrgpYHgpLfgpY3gpJ/gpL/g
pJXgpLDgpY3gpKTgpL4g4KS14KS/4KS24KWN4KS1IOCksOCkmuCkqOCkvjwvZGl2PjxkaXY+PGJy
PjwvZGl2PjxkaXY+2Ygg2K3ZitirINmC2LHYsdiqINmH2KfYsdio2LEg2KfZhNmG2LLYp9i5LCDY
s9in2LnYqSDYp9mE2YfYp9iv2Yog2KXYsCDZiNmB2YosINi52YYg2YXZhdinINmI2LLYp9ix2Kkg
2YjZh9mI2YTZhtiv2KfYjC4g2KXYsDwvZGl2PjxkaXY+2KfZhNij2YjZhCDYqNmF2KjYp9ix2YPY
qSDZhNmF2ZEuINio2K3YqyDZiti32YjZhCDZiNin2YTZhdi52K/Yp9iqINmj2aAsINmB2Yog2YXY
p9mK2Ygg2YTZhNis2LLYsSDZiNiz2YXZkdmK2Kog2YHZgtivLiDZhdinPC9kaXY+PGRpdj7Zgdix
2YbYs9mK2Kkg2KzYstmK2LHYqtmKINin2YTYq9in2YTYqyDZiNmF2YYuINmF2YPZhiDZh9mIINmE
2YPZiNmGINmF2K/ZitmG2Kkg2YjYqNix2YrYt9in2YbZitinLiDZo9mgINmI2YTZhSDYp9mE2YTZ
hyDYp9mE2YXYqtit2K/YqS48L2Rpdj48ZGl2Ptiq2YTZgyDZiNiq2LHZgyDZhNio2YjZhNmG2K/Y
p9iMINmC2K8sINmH2LDYpyDZiNis2YfYp9mGINin2YTYrtin2LfZgdipINin2YTZiNiy2LHYp9ih
INi52YYuPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5CZXN0LDwvZGl2PjxkaXY+WW91ciBGcmll
bmQ8L2Rpdj4NCjwvZGl2Pg0K
--e89a8f3baa71eda1b3053f7a2c28--