Python-digital-forensics-investigation-using-emails
メールを使用した調査
前の章では、ネットワークフォレンジックの重要性とプロセス、および関連する概念について説明しました。 この章では、デジタルフォレンジックにおける電子メールの役割とPythonを使用した調査について学習します。
調査における電子メールの役割
電子メールはビジネスコミュニケーションで非常に重要な役割を果たし、インターネット上で最も重要なアプリケーションの1つとして登場しました。 コンピュータからだけでなく、携帯電話やタブレットなどの他の電子機器からもメッセージだけでなくドキュメントを送信するのに便利なモードです。
電子メールのマイナス面は、犯罪者が会社に関する重要な情報を漏洩する可能性があることです。 したがって、デジタルフォレンジックにおける電子メールの役割は、近年増加しています。 デジタルフォレンジックでは、電子メールは重要な証拠と見なされ、電子メールヘッダー分析は、フォレンジックプロセス中に証拠を収集するために重要になりました。
捜査官は、電子メールの法医学を実行しながら、次の目標を持っています-
- 主な犯罪者を特定するには
- 必要な証拠を収集するには
- 調査結果を提示するには
- ケースを作成するには
メールフォレンジックの課題
現在の通信のほとんどは電子メールに依存しているため、電子メールフォレンジックは調査において非常に重要な役割を果たします。 ただし、電子メールの法医学調査員は、調査中に次の課題に直面する可能性があります-
偽のメール
電子メールフォレンジックの最大の課題は、ヘッダーなどの操作とスクリプト作成によって作成される偽の電子メールの使用です。 このカテゴリでは、犯罪者は一時的な電子メールも使用します。これは、登録されたユーザーが特定の期間が経過すると期限が切れる一時的なアドレスで電子メールを受信できるサービスです。
なりすまし
メールフォレンジックのもう1つの課題は、犯罪者が他人のメールを提示するために使用するなりすましです。 この場合、マシンは偽のIPアドレスと元のIPアドレスの両方を受け取ります。
匿名の再メール
ここで、電子メールサーバーは、さらに転送する前に電子メールメッセージから識別情報を取り除きます。 これは、電子メール調査の別の大きな課題につながります。
電子メールの法医学調査で使用される手法
メールフォレンジックは、送信の日付/時刻や送信者の意図などのその他の情報とともに、メッセージの実際の送信者と受信者を識別する証拠としての電子メールのソースとコンテンツの研究です。 これには、メタデータの調査、ポートスキャン、キーワード検索が含まれます。
電子メールの法医学調査に使用できる一般的な手法のいくつかは次のとおりです。
- ヘッダー分析
- サーバー調査
- ネットワークデバイス調査
- 送信者のメーラーの指紋
- ソフトウェア組み込み識別子
以下のセクションでは、電子メール調査のためにPythonを使用して情報を取得する方法を学習します。
EMLファイルからの情報の抽出
EMLファイルは、基本的にファイル形式の電子メールであり、電子メールメッセージの保存に広く使用されています。 これらは、Microsoft Outlook、Outlook Express、Windows Live Mailなどの複数の電子メールクライアント間で互換性のある構造化テキストファイルです。
EMLファイルには、電子メールヘッダー、本文コンテンツ、添付データがプレーンテキストとして保存されます。 base64を使用してバイナリデータをエンコードし、Quoted-Printable(QP)エンコードを使用してコンテンツ情報を保存します。 EMLファイルから情報を抽出するために使用できるPythonスクリプトは以下のとおりです-
まず、以下に示すように、次のPythonライブラリをインポートします-
from __future__ import print_function
from argparse import ArgumentParser, FileType
from email import message_from_file
import os
import quopri
import base64
上記のライブラリでは、EMQファイルからQPエンコードされた値をデコードするために quopri が使用されます。 base64でエンコードされたデータは、 base64 ライブラリを使用してデコードできます。
次に、コマンドラインハンドラーの引数を指定します。 以下に示すように、ここではEMLファイルへのパスになる引数を1つだけ受け入れることに注意してください-
if __name__ == '__main__':
parser = ArgumentParser('Extracting information from EML file')
parser.add_argument("EML_FILE",help="Path to EML File", type=FileType('r'))
args = parser.parse_args()
main(args.EML_FILE)
ここで、オブジェクトのようなファイルを読み取るために、電子メールライブラリから* message_from_file()という名前のメソッドを使用する main()関数を定義する必要があります。 ここで、以下に示すコードに示すように、 *emlfile という名前の結果変数を使用して、ヘッダー、本文コンテンツ、添付ファイル、およびその他のペイロード情報にアクセスします-
def main(input_file):
emlfile = message_from_file(input_file)
for key, value in emlfile._headers:
print("{}: {}".format(key, value))
print("\nBody\n")
if emlfile.is_multipart():
for part in emlfile.get_payload():
process_payload(part)
else:
process_payload(emlfile[1])
ここで、* get_payload()メソッドを使用してメッセージ本文コンテンツを抽出する process_payload()*メソッドを定義する必要があります。 * quopri.decodestring()*関数を使用してQPエンコードデータをデコードします。 また、電子メールの保存を適切に処理できるように、コンテンツのMIMEタイプを確認します。 以下のコードを確認してください-
def process_payload(payload):
print(payload.get_content_type() + "\n" + "=" * len(payload.get_content_type()))
body = quopri.decodestring(payload.get_payload())
if payload.get_charset():
body = body.decode(payload.get_charset())
else:
try:
body = body.decode()
except UnicodeDecodeError:
body = body.decode('cp1252')
if payload.get_content_type() == "text/html":
outfile = os.path.basename(args.EML_FILE.name) + "l"
open(outfile, 'w').write(body)
elif payload.get_content_type().startswith('application'):
outfile = open(payload.get_filename(), 'wb')
body = base64.b64decode(payload.get_payload())
outfile.write(body)
outfile.close()
print("Exported: {}\n".format(outfile.name))
else:
print(body)
上記のスクリプトを実行した後、コンソールにさまざまなペイロードとともにヘッダー情報を取得します。
Pythonを使用したMSGファイルの分析
電子メールメッセージにはさまざまな形式があります。 MSGは、Microsoft OutlookおよびExchangeで使用されるこの種の形式の1つです。 MSG拡張子のファイルには、ヘッダーとメインメッセージ本文、およびハイパーリンクと添付ファイル用のプレーンASCIIテキストが含まれている場合があります。
このセクションでは、Outlook APIを使用してMSGファイルから情報を抽出する方法を学習します。 次のPythonスクリプトはWindowsでのみ機能することに注意してください。 このためには、次のように pywin32 という名前のサードパーティPythonライブラリをインストールする必要があります-
pip install pywin32
今、示されているコマンドを使用して、次のライブラリをインポートします-
from __future__ import print_function
from argparse import ArgumentParser
import os
import win32com.client
import pywintypes
ここで、コマンドラインハンドラーの引数を指定しましょう。 ここでは、2つの引数を受け入れます。1つはMSGファイルへのパスで、もう1つは次のように目的の出力フォルダーです-
if __name__ == '__main__':
parser = ArgumentParser(‘Extracting information from MSG file’)
parser.add_argument("MSG_FILE", help="Path to MSG file")
parser.add_argument("OUTPUT_DIR", help="Path to output folder")
args = parser.parse_args()
out_dir = args.OUTPUT_DIR
if not os.path.exists(out_dir):
os.makedirs(out_dir)
main(args.MSG_FILE, args.OUTPUT_DIR)
次に、* main()関数を定義する必要があります。この関数では、 MAPI名前空間へのアクセスをさらに許可する Outlook API を設定するために、 win32com ライブラリを呼び出します。
def main(msg_file, output_dir):
mapi = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
msg = mapi.OpenSharedItem(os.path.abspath(args.MSG_FILE))
display_msg_attribs(msg)
display_msg_recipients(msg)
extract_msg_body(msg, output_dir)
extract_attachments(msg, output_dir)
次に、このスクリプトで使用しているさまざまな関数を定義します。 以下のコードは、* display_msg_attribs()*関数の定義を示しています。この関数を使用すると、件名、to、BCC、CC、サイズ、SenderName、送信済みなどのメッセージのさまざまな属性を表示できます。
def display_msg_attribs(msg):
attribs = [
'Application', 'AutoForwarded', 'BCC', 'CC', 'Class',
'ConversationID', 'ConversationTopic', 'CreationTime',
'ExpiryTime', 'Importance', 'InternetCodePage', 'IsMarkedAsTask',
'LastModificationTime', 'Links','ReceivedTime', 'ReminderSet',
'ReminderTime', 'ReplyRecipientNames', 'Saved', 'Sender',
'SenderEmailAddress', 'SenderEmailType', 'SenderName', 'Sent',
'SentOn', 'SentOnBehalfOfName', 'Size', 'Subject',
'TaskCompletedDate', 'TaskDueDate', 'To', 'UnRead'
]
print("\nMessage Attributes")
for entry in attribs:
print("{}: {}".format(entry, getattr(msg, entry, 'N/A')))
次に、メッセージを反復処理して受信者の詳細を表示する* display_msg_recipeints()*関数を定義します。
def display_msg_recipients(msg):
recipient_attrib = ['Address', 'AutoResponse', 'Name', 'Resolved', 'Sendable']
i = 1
while True:
try:
recipient = msg.Recipients(i)
except pywintypes.com_error:
break
print("\nRecipient {}".format(i))
print("=" * 15)
for entry in recipient_attrib:
print("{}: {}".format(entry, getattr(recipient, entry, 'N/A')))
i += 1
次に、メッセージから本文コンテンツ、HTML、およびプレーンテキストを抽出する* extract_msg_body()*関数を定義します。
def extract_msg_body(msg, out_dir):
html_data = msg.HTMLBody.encode('cp1252')
outfile = os.path.join(out_dir, os.path.basename(args.MSG_FILE))
open(outfile + ".bodyl", 'wb').write(html_data)
print("Exported: {}".format(outfile + ".bodyl"))
body_data = msg.Body.encode('cp1252')
open(outfile + ".body.txt", 'wb').write(body_data)
print("Exported: {}".format(outfile + ".body.txt"))
次に、添付データを目的の出力ディレクトリにエクスポートする* extract_attachments()*関数を定義します。
def extract_attachments(msg, out_dir):
attachment_attribs = ['DisplayName', 'FileName', 'PathName', 'Position', 'Size']
i = 1 # Attachments start at 1
while True:
try:
attachment = msg.Attachments(i)
except pywintypes.com_error:
break
すべての機能が定義されたら、次のコード行ですべての属性をコンソールに出力します-
print("\nAttachment {}".format(i))
print("=" * 15)
for entry in attachment_attribs:
print('{}: {}'.format(entry, getattr(attachment, entry,"N/A")))
outfile = os.path.join(os.path.abspath(out_dir),os.path.split(args.MSG_FILE)[-1])
if not os.path.exists(outfile):
os.makedirs(outfile)
outfile = os.path.join(outfile, attachment.FileName)
attachment.SaveAsFile(outfile)
print("Exported: {}".format(outfile))
i += 1
上記のスクリプトを実行した後、コンソールウィンドウでメッセージの属性とその添付ファイルを取得し、出力ディレクトリのいくつかのファイルを取得します。
Pythonを使用してGoogle TakeoutからMBOXファイルを構造化する
MBOXファイルは、内部に保存されたメッセージを分割する特別な形式のテキストファイルです。 多くの場合、UNIXシステム、Thunderbolt、およびGoogle Takeoutに関連して見つかります。
このセクションには、Google Takeoutsから取得したMBOXファイルを構造化するPythonスクリプトが表示されます。 ただし、その前に、GoogleアカウントまたはGmailアカウントを使用してこれらのMBOXファイルを生成する方法を知っておく必要があります。
GoogleアカウントメールボックスをMBX形式で取得する
Googleアカウントのメールボックスを取得することは、Gmailアカウントのバックアップを取ることを意味します。 バックアップは、個人的または職業上のさまざまな理由で取得できます。 GoogleはGmailデータのバックアップを提供することに注意してください。 GoogleアカウントのメールボックスをMBOX形式に取得するには、以下の手順に従う必要があります-
- [マイアカウント]ダッシュボードを開きます。
- [個人情報とプライバシー]セクションに移動し、[コンテンツリンクの制御]を選択します。
- 新しいアーカイブを作成するか、既存のアーカイブを管理できます。 [アーカイブの作成]リンクをクリックすると、含めるGoogleサービスごとにチェックボックスが表示されます。
- 製品を選択した後、リストから選択する配信方法とともに、アーカイブのファイルタイプと最大サイズを自由に選択できます。
- 最後に、このバックアップをMBOX形式で取得します。
Pythonコード
さて、上記のMBOXファイルは、以下に示すようにPythonを使用して構造化できます-
まず、次のようにPythonライブラリをインポートする必要があります-
from __future__ import print_function
from argparse import ArgumentParser
import mailbox
import os
import time
import csv
from tqdm import tqdm
import base64
MBOXファイルの解析に使用される mailbox ライブラリを除き、すべてのライブラリが以前のスクリプトで使用および説明されています。
次に、コマンドラインハンドラーの引数を指定します。 ここでは、2つの引数を受け入れます。1つはMBOXファイルへのパスで、もう1つは目的の出力フォルダーです。
if __name__ == '__main__':
parser = ArgumentParser('Parsing MBOX files')
parser.add_argument("MBOX", help="Path to mbox file")
parser.add_argument(
"OUTPUT_DIR",help = "Path to output directory to write report ""and exported content")
args = parser.parse_args()
main(args.MBOX, args.OUTPUT_DIR)
ここで、* main()関数を定義し、そのパスを指定してMBOXファイルを解析できるように、メールボックスライブラリの *mbox クラスを呼び出します-
def main(mbox_file, output_dir):
print("Reading mbox file")
mbox = mailbox.mbox(mbox_file, factory=custom_reader)
print("{} messages to parse".format(len(mbox)))
今、次のように*メールボックス*ライブラリのリーダーメソッドを定義します-
def custom_reader(data_stream):
data = data_stream.read()
try:
content = data.decode("ascii")
except (UnicodeDecodeError, UnicodeEncodeError) as e:
content = data.decode("cp1252", errors="replace")
return mailbox.mboxMessage(content)
今、次のようにさらに処理するためのいくつかの変数を作成します-
parsed_data = []
attachments_dir = os.path.join(output_dir, "attachments")
if not os.path.exists(attachments_dir):
os.makedirs(attachments_dir)
columns = [
"Date", "From", "To", "Subject", "X-Gmail-Labels", "Return-Path", "Received",
"Content-Type", "Message-ID","X-GM-THRID", "num_attachments_exported", "export_path"]
次に、 tqdm を使用して進行状況バーを生成し、次のように反復プロセスを追跡します-
for message in tqdm(mbox):
msg_data = dict()
header_data = dict(message._headers)
for hdr in columns:
msg_data[hdr] = header_data.get(hdr, "N/A")
次に、天気メッセージにペイロードがあるかどうかを確認します。 それがあれば、次のように* write_payload()*メソッドを定義します-
if len(message.get_payload()):
export_path = write_payload(message, attachments_dir)
msg_data['num_attachments_exported'] = len(export_path)
msg_data['export_path'] = ", ".join(export_path)
次に、データを追加する必要があります。 次に、次のように* create_report()*メソッドを呼び出します-
parsed_data.append(msg_data)
create_report(
parsed_data, os.path.join(output_dir, "mbox_report.csv"), columns)
def write_payload(msg, out_dir):
pyld = msg.get_payload()
export_path = []
if msg.is_multipart():
for entry in pyld:
export_path += write_payload(entry, out_dir)
else:
content_type = msg.get_content_type()
if "application/" in content_type.lower():
content = base64.b64decode(msg.get_payload())
export_path.append(export_content(msg, out_dir, content))
elif "image/" in content_type.lower():
content = base64.b64decode(msg.get_payload())
export_path.append(export_content(msg, out_dir, content))
elif "video/" in content_type.lower():
content = base64.b64decode(msg.get_payload())
export_path.append(export_content(msg, out_dir, content))
elif "audio/" in content_type.lower():
content = base64.b64decode(msg.get_payload())
export_path.append(export_content(msg, out_dir, content))
elif "text/csv" in content_type.lower():
content = base64.b64decode(msg.get_payload())
export_path.append(export_content(msg, out_dir, content))
elif "info/" in content_type.lower():
export_path.append(export_content(msg, out_dir,
msg.get_payload()))
elif "text/calendar" in content_type.lower():
export_path.append(export_content(msg, out_dir,
msg.get_payload()))
elif "text/rtf" in content_type.lower():
export_path.append(export_content(msg, out_dir,
msg.get_payload()))
else:
if "name=" in msg.get('Content-Disposition', "N/A"):
content = base64.b64decode(msg.get_payload())
export_path.append(export_content(msg, out_dir, content))
elif "name=" in msg.get('Content-Type', "N/A"):
content = base64.b64decode(msg.get_payload())
export_path.append(export_content(msg, out_dir, content))
return export_path
上記のif-elseステートメントは理解しやすいことに注意してください。 今、私たちは次のように msg オブジェクトからファイル名を抽出するメソッドを定義する必要があります-
def export_content(msg, out_dir, content_data):
file_name = get_filename(msg)
file_ext = "FILE"
if "." in file_name: file_ext = file_name.rsplit(".", 1)[-1]
file_name = "{}_{:.4f}.{}".format(file_name.rsplit(".", 1)[0], time.time(), file_ext)
file_name = os.path.join(out_dir, file_name)
今、次のコード行の助けを借りて、実際にファイルをエクスポートできます-
if isinstance(content_data, str):
open(file_name, 'w').write(content_data)
else:
open(file_name, 'wb').write(content_data)
return file_name
さて、次のようにこれらのファイルの名前を正確に表すために*メッセージ*からファイル名を抽出する関数を定義しましょう-
def get_filename(msg):
if 'name=' in msg.get("Content-Disposition", "N/A"):
fname_data = msg["Content-Disposition"].replace("\r\n", " ")
fname = [x for x in fname_data.split("; ") if 'name=' in x]
file_name = fname[0].split("=", 1)[-1]
elif 'name=' in msg.get("Content-Type", "N/A"):
fname_data = msg["Content-Type"].replace("\r\n", " ")
fname = [x for x in fname_data.split("; ") if 'name=' in x]
file_name = fname[0].split("=", 1)[-1]
else:
file_name = "NO_FILENAME"
fchars = [x for x in file_name if x.isalnum() or x.isspace() or x == "."]
return "".join(fchars)
これで、次のように* create_report()*関数を定義してCSVファイルを作成できます-
def create_report(output_data, output_file, columns):
with open(output_file, 'w', newline="") as outfile:
csvfile = csv.DictWriter(outfile, columns)
csvfile.writeheader()
csvfile.writerows(output_data)
上記のスクリプトを実行すると、CSVレポートとディレクトリが添付ファイルでいっぱいになります。