Python-digital-forensics-quick-guide

提供:Dev Guides
移動先:案内検索

Pythonデジタルフォレンジック-はじめに

この章では、デジタルフォレンジックとは何か、およびその歴史的レビューを紹介します。 また、実生活でデジタルフォレンジックを適用できる場所とその制限についても理解します。

デジタルフォレンジックとは何ですか?

デジタルフォレンジックは、電子デバイスに存在するデジタル証拠を分析、検査、識別、および回復するフォレンジックサイエンスの分野として定義できます。 一般に、刑法および私的調査に使用されます。

たとえば、誰かが電子デバイス上のデータを盗む場合に、デジタルフォレンジックが証拠を抽出することに頼ることができます。

デジタルフォレンジックの簡単な歴史的レビュー

コンピュータ犯罪の歴史とデジタルフォレンジックの歴史的レビューは、このセクションで以下のように説明されています-

1970年代〜1980年代:最初のコンピューター犯罪

この10年以前は、コンピューター犯罪は認められていません。 しかし、それが起こることになっている場合、当時の既存の法律はそれらに対処しました。 その後、1978年にフロリダのコンピューター犯罪法で最初のコンピューター犯罪が認められました。これには、コンピューターシステム上のデータの不正な変更または削除に対する法律が含まれています。 しかし、時間の経過とともに、技術の進歩により、犯されるコンピューター犯罪の範囲も増加しました。 著作権、プライバシー、児童ポルノに関連する犯罪に対処するために、他のさまざまな法律が可決されました。

1980年代から1990年代:開発の10年

この10年は、デジタルフォレンジックの開発10年でした。これは、クリフストールがMarkus Hessという名前のハッカーを追跡した最初の調査(1986年)によるものです。 この期間に、2種類のデジタルフォレンジック分野が開発されました。1つ目は、趣味としてそれを採用した実践者が開発したアドホックツールとテクニックの助けを借りて、2つ目は科学コミュニティによって開発されました。 1992年には、「コンピューターフォレンジック」という用語が学術文献で使用されました。

2000s-2010s:標準化の10年

デジタルフォレンジックが特定のレベルにまで発展した後、調査の実行中に従うことができる特定の標準を作成する必要がありました。 したがって、さまざまな科学機関や機関がデジタルフォレンジックのガイドラインを公開しています。 2002年に、デジタル証拠に関する科学作業部会(SWGDE)は、「コンピューターフォレンジックのベストプラクティス」という名前の論文を発表しました。 キャップのもう一つの羽毛は、欧州主導の国際条約であり、*「サイバー犯罪に関する条約」*は43カ国によって署名され、16カ国によって批准されました。 そのような基準の後でも、研究者によって特定されたいくつかの問題を解決する必要があります。

デジタルフォレンジックのプロセス

1978年に初めてコンピューター犯罪が発生して以来、デジタル犯罪活動は大幅に増加しています。 この増加により、それらに対処するための構造化された方法が必要です。 1984年に、正式なプロセスが導入され、その後、多数の新しく改善されたコンピューターフォレンジック調査プロセスが開発されました。

コンピュータフォレンジック調査プロセスには、以下で説明する3つの主要なフェーズが含まれます-

フェーズ1:展示品の取得またはイメージング

デジタルフォレンジックの最初のフェーズでは、デジタルシステムの状態を保存して、後で分析できるようにします。 写真や血液サンプルなどを撮ることに非常によく似ています。 犯罪現場から。 たとえば、ハードディスクまたはRAMの割り当て済み領域と未割り当て領域のイメージをキャプチャすることが含まれます。

フェーズ2:分析

このフェーズの入力は、取得フェーズで取得されたデータです。 ここでは、証拠を特定するためにこのデータを調べました。 この段階では、次の3種類の証拠が得られます-

  • * Inculpatory証拠*-これらの証拠は、特定の歴史をサポートしています。
  • 除外証拠-これらの証拠は、特定の歴史と矛盾しています。
  • 改ざんの証拠-これらの証拠は、システムが識別を回避するために調整されたことを示しています。 削除されたファイルを回復するために、ファイルとディレクトリの内容を調べることが含まれます。

フェーズ3:プレゼンテーションまたはレポート

名前が示すように、このフェーズでは調査の結論と対応する証拠を提示します。

デジタルフォレンジックの応用

デジタルフォレンジックは、デジタルデバイスに含まれる証拠の収集、分析、および保存を扱います。 デジタルフォレンジックの使用は、アプリケーションによって異なります。 前述のように、それは主に次の2つのアプリケーションで使用されています-

刑法

刑法では、裁判所で仮説を支持または反対するために証拠が収集されます。 法医学の手順は、犯罪捜査で使用される手順と非常に似ていますが、法的要件と制限が異なります。

民間調査

主に企業の世界では、民間調査にデジタルフォレンジックを使用しています。 これは、従業員が会社のポリシーに違反する違法行為をコンピューターで実行している可能性があると企業が疑っている場合に使用されます。 デジタルフォレンジックは、企業または個人がデジタル不正行為を調査する際に取る最適なルートの1つです。

デジタルフォレンジックの枝

デジタル犯罪はコンピューターだけに限定されませんが、ハッカーや犯罪者はタブレット、スマートフォンなどの小さなデジタルデバイスを使用しています。 非常に大規模でも。 一部のデバイスには揮発性メモリがあり、その他のデバイスには不揮発性メモリがあります。 したがって、デバイスの種類に応じて、デジタルフォレンジックには次の分岐があります-

コンピューターフォレンジック

デジタルフォレンジックのこの部門は、コンピューター、組み込みシステム、USBドライブなどの静的メモリを扱います。 ログからドライブ上の実際のファイルまでの幅広い情報は、コンピューターフォレンジックで調査できます。

モバイルフォレンジック

これは、モバイルデバイスからのデータの調査を扱います。 このブランチは、モバイルデバイスに位置に関する有用な情報を提供するのに役立つ組み込みの通信システムがあるという意味で、コンピューターフォレンジックとは異なります。

ネットワークフォレンジック

これは、情報収集、証拠収集、または侵入検知を目的とした、ローカルおよびWAN(ワイドエリアネットワーク)の両方のコンピューターネットワークトラフィックの監視と分析を扱います。

データベースフォレンジック

デジタルフォレンジックのこの部門は、データベースとそのメタデータのフォレンジック研究を扱っています。

デジタルフォレンジック調査に必要なスキル

デジタルフォレンジック検査官は、ハッカーの追跡、盗まれたデータの回復、コンピューター攻撃を追跡してソースに戻り、コンピューターに関する他の種類の調査を支援します。 以下で説明するように、デジタルフォレンジック試験官になるために必要な主要なスキルの一部-

優れた思考能力

デジタルフォレンジック調査員は優れた思想家でなければならず、出力を取得するための特定の割り当てにさまざまなツールや手法を適用できる必要があります。 彼/彼女は異なるパターンを見つけて、それらの間の相関関係を作ることができなければなりません。

技術的なスキル

この分野にはネットワーク、デジタルシステムの相互作用の知識が必要なため、デジタルフォレンジック試験官には優れた技術スキルが必要です。

サイバーセキュリティへの情熱

デジタルフォレンジックの分野はすべてサイバー犯罪を解決することであり、これは退屈な作業であるため、エースのデジタルフォレンジック調査員になるには多くの情熱が必要です。

コミュニケーションスキル

さまざまなチームと連携し、不足しているデータや情報を抽出するには、優れたコミュニケーションスキルが必要です。

レポート作成に熟練

取得と分析の実装が成功した後、デジタル法医学検査官は、すべての調査結果を最終レポートとプレゼンテーションに言及する必要があります。 したがって、彼/彼女はレポート作成の優れたスキルと細部への注意を持っている必要があります。

制限事項

ここで説明するように、デジタルフォレンジック調査には特定の制限があります-

説得力のある証拠を作成する必要がある

デジタルフォレンジック調査の大きな失敗の1つは、データが簡単に改ざんされる可能性があるため、審査官が法廷での証拠に必要な基準を遵守する必要があることです。 一方、法医学捜査官は、法廷で説得力のある証拠を提示するために、法的要件、証拠の取り扱い、および文書化手順に関する完全な知識を持っている必要があります。

調査ツール

デジタル調査の有効性は、デジタル法医学審査官の専門知識と適切な調査ツールの選択に完全に依存しています。 使用するツールが指定された基準に準拠していない場合、法廷で、裁判官は証拠を否定することができます。

聴衆の技術的知識の欠如

もう1つの制限は、一部の個人がコンピューターフォレンジックに完全に精通していないことです。したがって、多くの人々はこの分野を理解していません。 調査員は、誰もが結果を理解できるように、調査結果を裁判所に確実に伝える必要があります。

Cost

デジタル証拠を作成して保存することは非常に費用がかかります。 したがって、このプロセスは、費用を支払う余裕のない多くの人々によって選択されることはありません。

Pythonデジタルフォレンジック-はじめに

前の章では、デジタルフォレンジックの基本、その利点と制限について学びました。 この章では、このデジタルフォレンジック調査で使用している重要なツールであるPythonを快適に使用できるようにします。

Pythonがデジタルフォレンジックに適している理由

Pythonは人気のあるプログラミング言語であり、サイバーセキュリティ、侵入テスト、デジタルフォレンジック調査のツールとして使用されます。 Pythonをデジタルフォレンジックのツールとして選択する場合、タスクを完了するために他のサードパーティソフトウェアは必要ありません。

デジタルフォレンジックプロジェクトに最適なPythonプログラミング言語のユニークな機能のいくつかを以下に示します-

  • シンタックスのシンタックス-Pythonのシンタックスは他の言語に比べて単純であるため、デジタルフォレンジックの学習と使用が容易になります。
  • 包括的な組み込みモジュール-Pythonの包括的な組み込みモジュールは、完全なデジタルフォレンジック調査を実行するための優れた支援です。
  • ヘルプとサポート-オープンソースのプログラミング言語であるPythonは、開発者とユーザーのコミュニティから優れたサポートを受けています。

Pythonの機能

Pythonは、高レベルで、解釈された、インタラクティブでオブジェクト指向のスクリプト言語であり、次の機能を提供します-

  • 習得が容易-Pythonは、より少ないキーワードと最も単純な構造を持つため、開発者にとって使いやすく、習得が容易な言語です。
  • 表現力豊かで読みやすい-Python言語は本質的に表現力があります。したがって、そのコードはより理解しやすく、読みやすくなります。
  • クロスプラットフォーム互換-Pythonはクロスプラットフォーム互換の言語であるため、UNIX、Windows、Macintoshなどのさまざまなプラットフォームで効率的に実行できます。
  • インタラクティブモードプログラミング-Pythonはプログラミング用のインタラクティブモードをサポートしているため、コードのインタラクティブなテストとデバッグを行うことができます。
  • さまざまなモジュールと関数を提供します-Pythonには、スクリプト用に豊富なモジュールと関数のセットを使用できる大きな標準ライブラリがあります。
  • 動的型チェックをサポート-Pythonは動的型チェックをサポートし、非常に高レベルの動的データ型を提供します。
  • * GUIプログラミング*-PythonはGUIプログラミングをサポートして、グラフィカルユーザーインターフェイスを開発します。
  • 他のプログラミング言語との統合-Pythonは、C、C ++、JAVAなどの他のプログラミング言語と簡単に統合できます。

Pythonをインストールする

Pythonディストリビューションは、Windows、UNIX、Linux、Macなどのさまざまなプラットフォームで利用できます。 プラットフォームごとにバイナリコードをダウンロードするだけです。 プラットフォームのバイナリコードが利用できない場合、ソースコードを手動でコンパイルできるようにCコンパイラが必要です。

このセクションでは、さまざまなプラットフォームでのPythonのインストールについて詳しく説明します。

UnixおよびLinuxでのPythonインストール

以下に示す手順に従って、Unix/LinuxマシンにPythonをインストールできます。

  • ステップ2 *-Unix/Linuxで利用可能なzip形式のソースコードをダウンロードします。
  • ステップ3 *-ダウンロードした圧縮ファイルを解凍します。

ステップ4 *-オプションをカスタマイズする場合は、 *Modules/Setup file を編集できます。

  • ステップ5 *-インストールを完了するには、次のコマンドを使用します-
run ./configure script
make
make install

上記の手順を正常に完了すると、Pythonは標準の場所 /usr/local/bin にインストールされ、ライブラリは /usr/local/lib/pythonXX にインストールされます(XXはPythonのバージョンです)。

WindowsでのPythonインストール

次の簡単な手順に従って、WindowsマシンにPythonをインストールできます。

ステップ2 *-Windowsインストーラー *python-XYZ.msi ファイルをダウンロードします。XYZはインストールする必要のあるバージョンです。

  • ステップ3 *-インストーラファイルをローカルマシンに保存した後、MSIファイルを実行します。
  • ステップ4 *-ダウンロードしたファイルを実行し、Pythonインストールウィザードを起動します。

MacintoshでのPythonインストール

Mac OS XにPython 3をインストールするには、 Homebrew という名前のパッケージインストーラーを使用する必要があります。

あなたはあなたのシステム上にそれを持っていない場合、Homebrewをインストールするために次のコマンドを使用することができます-

$ ruby -e "$(curl -fsSL
https://raw.githubusercontent.com/Homebrew/install/master/install)"

あなたがパッケージマネージャを更新する必要がある場合、それは次のコマンドの助けを借りて行うことができます-

$ brew update

さて、次のコマンドを使用して、システムにPython3をインストールします-

$ brew install python3

PATHを設定する

Pythonインストールのパスを設定する必要がありますが、これはUNIX、WINDOWS、MACなどのプラットフォームによって異なります。

Unix/Linuxでのパス設定

次のオプションを使用して、Unix/Linuxでパスを設定できます-

  • * cshシェルを使用している場合*- setenv PATH "$ PATH:/usr/local/bin/python" と入力し、Enterを押します。
  • * bashシェルを使用する場合(Linux)- *export ATH = "$ PATH:/usr/local/bin/python" と入力し、Enterを押します。
  • * shまたはkshシェルを使用している場合*- PATH = "$ PATH:/usr/local/bin/python" と入力し、Enterを押します。

Windowsでのパス設定

コマンドプロンプトで「 path%path%; C:\ Python 」と入力し、Enterキーを押します。

Pythonを実行する

次の3つの方法のいずれかを選択して、Pythonインタープリターを起動できます-

方法1:インタラクティブインタープリターを使用する

コマンドラインインタープリターまたはシェルを提供するシステムは、Pythonの起動に簡単に使用できます。 たとえば、Unix、DOSなど。 以下の手順に従って、対話型インタープリターでコーディングを開始できます-

ステップ1 *-コマンドラインで *python と入力します。

  • ステップ2 *-以下に示すコマンドを使用して、インタラクティブインタープリターですぐにコーディングを開始します-
$python # Unix/Linux
or
python% # Unix/Linux
or
C:> python # Windows/DOS

方法2:コマンドラインからスクリプトを使用する

アプリケーションでインタープリターを呼び出すことにより、コマンドラインでPythonスクリプトを実行することもできます。 以下に示すコマンドを使用できます-

$python script.py # Unix/Linux
or
python% script.py # Unix/Linux
or
C: >python script.py # Windows/DOS

方法3:統合開発環境

システムにPythonをサポートするGUIアプリケーションがある場合、そのGUI環境からPythonを実行できます。 さまざまなプラットフォーム用のIDEの一部を以下に示します-

  • Unix IDE -UNIXにはPython用のIDLE IDEがあります。
  • Windows IDE -WindowsにはPythonWinがあります。これは、GUIとともにPythonの最初のWindowsインターフェイスです。
  • Macintosh IDE -Macintoshには、メインWebサイトから入手可能なIDLE IDEがあり、MacBinaryまたはBinHex’dファイルとしてダウンロードできます。

アーティファクトレポート

ローカルシステムでのPythonコマンドのインストールと実行に慣れたので、フォレンジックの概念について詳しく説明します。 この章では、Pythonデジタルフォレンジックのアーティファクトの処理に関連するさまざまな概念について説明します。

レポート作成の必要性

デジタルフォレンジックのプロセスには、第3フェーズとしてのレポートが含まれます。 これは、デジタルフォレンジックプロセスの最も重要な部分の1つです。 次の理由により、レポートの作成が必要です-

  • それは、デジタル法医学審査官が調査プロセスとその調査結果を概説する文書です。
  • 優れたデジタルフォレンジックレポートを別の審査官が参照して、同じリポジトリで同じ結果を得ることができます。
  • これは、デジタル証拠の1と0で見つかった事実を含む技術的および科学的な文書です。

レポート作成の一般的なガイドライン

レポートは、読者に情報を提供するために書かれており、強固な基盤から始めなければなりません。 調査者は、一般的なガイドラインや標準を使用せずにレポートを作成すると、調査結果を効率的に提示するのが困難になります。 デジタルフォレンジックレポートを作成する際に従う必要があるいくつかの一般的なガイドラインを以下に示します-

  • 概要-読者がレポートの目的を確認できるように、レポートには情報の簡単な概要が含まれている必要があります。
  • 使用するツール-目的を含め、デジタルフォレンジックのプロセスを実行するために使用されたツールに言及する必要があります。
  • リポジトリ-誰かのコンピューターを調査し、証拠の概要と、電子メール、内部検索履歴などの関連資料の分析を調査した後、ケースが明確に提示されるようにレポートに含める必要があるとします。
  • 弁護士への推奨事項-レポートには、レポートの調査結果に基づいて調査を続行または中止するためのアドバイスが必要です。

異なるタイプのレポートの作成

上記のセクションでは、デジタルフォレンジックのレポートの重要性と、それを作成するためのガイドラインについて知りました。 さまざまな種類のレポートを作成するためのPythonの形式のいくつかを以下で説明します-

CSVレポート

レポートの最も一般的な出力形式の1つは、CSVスプレッドシートレポートです。 以下に示すように、Pythonコードを使用して、処理されたデータのレポートを作成するCSVを作成できます-

まず、スプレッドシートを書くための便利なライブラリをインポートします-

from __future__ import print_function
import csv
import os
import sys

今、次のメソッドを呼び出します-

Write_csv(TEST_DATA_LIST, ["Name", "Age", "City", "Job description"], os.getcwd())

次のグローバル変数を使用して、サンプルデータタイプを表します-

TEST_DATA_LIST = [[Ram", 32, Bhopal, Manager],
   ["Raman", 42, Indore, Engg.],
   ["Mohan", 25, Chandigarh, HR],
   ["Parkash", 45, Delhi, IT]]

次に、さらに操作を進めるためのメソッドを定義しましょう。 ファイルを「w」モードで開き、newlineキーワード引数を空の文字列に設定します。

def Write_csv(data, header, output_directory, name = None):
   if name is None:
      name = "report1.csv"
   print("[+] Writing {} to {}".format(name, output_directory))

   with open(os.path.join(output_directory, name), "w", newline = "") as \ csvfile:
      writer = csv.writer(csvfile)
      writer.writerow(header)
      writer.writerow(data)

上記のスクリプトを実行すると、report1.csvファイルに保存されている次の詳細が取得されます。

Name Age City Designation
Ram 32 Bhopal Managerh
Raman 42 Indore Engg
Mohan 25 Chandigarh HR
Parkash 45 Delhi IT

Excelレポート

レポートのもう1つの一般的な出力形式は、Excel(.xlsx)スプレッドシートレポートです。 テーブルを作成し、Excelを使用してグラフをプロットすることもできます。 以下に示すように、Pythonコードを使用して、処理されたデータのレポートをExcel形式で作成できます。

まず、スプレッドシートを作成するためのXlsxWriterモジュールをインポートします-

import xlsxwriter

次に、ワークブックオブジェクトを作成します。 このために、Workbook()コンストラクターを使用する必要があります。

workbook = xlsxwriter.Workbook('report2.xlsx')

次に、add_worksheet()モジュールを使用して新しいワークシートを作成します。

worksheet = workbook.add_worksheet()

次に、次のデータをワークシートに書き込みます-

report2 = (['Ram', 32, ‘Bhopal’],['Mohan',25, ‘Chandigarh’] ,['Parkash',45, ‘Delhi’])

row = 0
col = 0

あなたはこのデータを反復処理し、次のように書くことができます-

for item, cost in (a):
   worksheet.write(row, col, item)
   worksheet.write(row, col+1, cost)
   row + = 1

次に、close()メソッドを使用してこのExcelファイルを閉じます。

workbook.close()

上記のスクリプトは、次のデータを持つreport2.xlsxという名前のExcelファイルを作成します-

Ram 32 Bhopal
Mohan 25 Chandigarh
Parkash 45 Delhi

調査取得メディア

調査員は、調査結果を正確に思い出したり、調査のすべての部分をまとめるために、詳細な調査メモを用意することが重要です。 スクリーンショットは、特定の調査のために実行された手順を追跡するのに非常に役立ちます。 次のPythonコードの助けを借りて、スクリーンショットを撮り、将来の使用のためにハードディスクに保存することができます。

まず、次のコマンドを使用してpyscreenshotという名前のPythonモジュールをインストールします-

Pip install pyscreenshot

今、示されているように必要なモジュールをインポートします-

import pyscreenshot as ImageGrab

スクリーンショットを取得するには、次のコード行を使用します-

image = ImageGrab.grab()

次のコード行を使用して、指定された場所にスクリーンショットを保存します-

image.save('d:/image123.png')

今、あなたはグラフとしてスクリーンショットをポップアップしたい場合は、次のPythonコードを使用することができます-

import numpy as np
import matplotlib.pyplot as plt
import pyscreenshot as ImageGrab
imageg = ImageGrab.grab()
plt.imshow(image, cmap='gray', interpolation='bilinear')
plt.show()

Pythonデジタルモバイルデバイスフォレンジック

この章では、モバイルデバイス上のPythonデジタルフォレンジックと関連する概念について説明します。

前書き

モバイルデバイスフォレンジックは、モバイルデバイスの取得と分析を行い、調査対象のデジタル証拠を回復するデジタルフォレンジックの分野です。 モバイルデバイスには、位置に関する有用な情報を提供するのに役立つ組み込みの通信システムがあるため、このブランチはコンピューターフォレンジックとは異なります。

デジタルフォレンジックではスマートフォンの使用が日々増加していますが、それでもその不均一性のために非標準であると考えられています。 一方、ハードディスクなどのコンピューターハードウェアは標準と見なされ、安定した分野としても開発されています。 デジタルフォレンジック業界では、スマートフォンなどの一時的な証拠を持つ非標準デバイスに使用される技術について多くの議論があります。

モバイルデバイスから抽出可能なアーティファクト

最新のモバイルデバイスは、コールログまたはSMSメッセージのみを持つ古い電話と比較して、多くのデジタル情報を所有しています。 したがって、モバイルデバイスは、ユーザーに関する多くの洞察を調査員に提供できます。 モバイルデバイスから抽出できるいくつかのアーティファクトは以下のとおりです-

  • メッセージ-これらは、所有者の心の状態を明らかにすることができ、調査員に以前の未知の情報を提供することもできる便利なアーティファクトです。
  • ロケーション履歴-ロケーション履歴データは、人の特定の場所について検証するために調査員が使用できる有用なアーティファクトです。
  • インストールされているアプリケーション-インストールされているアプリケーションの種類にアクセスすることにより、調査員はモバイルユーザーの習慣と思考についての洞察を得ます。

エビデンスのソースとPythonでの処理

スマートフォンには、主要な証拠のソースとしてSQLiteデータベースとPLISTファイルがあります。 このセクションでは、Pythonで証拠のソースを処理します。

PLISTファイルの分析

PLIST(プロパティリスト)は、特にiPhoneデバイスにアプリケーションデータを保存するための柔軟で便利な形式です。 拡張子 .plist を使用します。 バンドルとアプリケーションに関する情報を保存するために使用されるこの種のファイル。 XMLbinary の2つの形式を使用できます。 次のPythonコードは、PLISTファイルを開いて読み取ります。 これに進む前に、独自の Info.plist ファイルを作成する必要があることに注意してください。

まず、次のコマンドで biplist という名前のサードパーティライブラリをインストールします-

Pip install biplist

今、plistファイルを処理するためにいくつかの有用なライブラリをインポートします-

import biplist
import os
import sys

さて、メインメソッドの下で次のコマンドを使用して、plistファイルを変数に読み込むことができます-

def main(plist):
   try:
      data = biplist.readPlist(plist)
   except (biplist.InvalidPlistException,biplist.NotBinaryPlistException) as e:
print("[-] Invalid PLIST file - unable to be opened by biplist")
sys.exit(1)

これで、この変数からコンソール上のデータを読み取るか、直接印刷することができます。

SQLiteデータベース

SQLiteは、モバイルデバイスのプライマリデータリポジトリとして機能します。 SQLiteは、自己完結型のサーバーなしのゼロ構成のトランザクションSQLデータベースエンジンを実装するインプロセスライブラリです。 それはゼロ構成のデータベースであり、他のデータベースとは異なり、システムで構成する必要はありません。

初心者またはSQLiteデータベースに慣れていない場合は、リンクlink:/sqlite/index [www.finddevguides.com/sqlite/index]をたどることができます。さらに、リンクlink:/sqlite/sqlite_python [www.finddevguidesをたどることができます。 .com/sqlite/sqlite_python] PythonでSQLiteの詳細を知りたい場合に備えて。

モバイルフォレンジック中に、モバイルデバイスの sms.db ファイルを操作して、 message テーブルから貴重な情報を抽出できます。 Pythonには、SQLiteデータベースと接続するための sqlite3 という名前の組み込みライブラリがあります。 次のコマンドで同じものをインポートできます-

import sqlite3

今、次のコマンドの助けを借りて、データベースに接続できます、モバイルデバイスの場合は sms.db と言います-

Conn = sqlite3.connect(‘sms.db’)
C = conn.cursor()

ここで、Cはデータベースオブジェクトとやり取りできるカーソルオブジェクトです。

今、特定のコマンドを実行する場合、たとえば* abcテーブル*から詳細を取得する場合、次のコマンドを使用して実行できると仮定します-

c.execute(“Select * from abc”)
c.close()

上記のコマンドの結果は cursor オブジェクトに保存されます。 同様に、* fetchall()*メソッドを使用して、操作可能な変数に結果をダンプできます。

次のコマンドを使用して、 sms.db のメッセージテーブルの列名データを取得できます-

c.execute(“pragma table_info(message)”)
table_data = c.fetchall()
columns = [x[1] for x in table_data

ここで、SQLite環境内のさまざまな環境変数と状態フラグを制御するために使用される特別なコマンドであるSQLite PRAGMAコマンドを使用していることに注意してください。 上記のコマンドでは、* fetchall()*メソッドは結果のタプルを返します。 各列の名前は、各タプルの最初のインデックスに保存されます。

今、次のコマンドの助けを借りて、すべてのデータについてテーブルを照会し、 data_msg という名前の変数に格納できます-

c.execute(“Select * from message”)
data_msg = c.fetchall()

上記のコマンドは変数にデータを保存し、さらに* csv.writer()*メソッドを使用してCSVファイルに上記のデータを書き込むこともできます。

iTunesのバックアップ

iPhoneのモバイルフォレンジックは、iTunesによって作成されたバックアップで実行できます。 科学捜査官は、iTunesを通じて取得したiPhoneの論理バックアップの分析に依存しています。 iTunesはAFC(Appleファイル接続)プロトコルを使用してバックアップを取ります。 また、バックアッププロセスでは、エスクローキーレコード以外のiPhone上の内容は変更されません。

さて、デジタルフォレンジックの専門家がiTunesバックアップのテクニックを理解することがなぜ重要なのかという疑問が生じます。 iPhoneを同期するためにコンピューターを使用すると、iPhone上のほとんどの情報がコンピューターにバックアップされる可能性が高いため、iPhoneの代わりに容疑者のコンピューターに直接アクセスできる場合は重要です。

バックアップのプロセスとその場所

Apple製品がコンピューターにバックアップされるたびに、iTunes製品と同期され、デバイスの一意のIDを持つ特定のフォルダーが作成されます。 最新のバックアップ形式では、ファイルはファイル名の最初の2つの16進文字を含むサブフォルダーに保存されます。 これらのバックアップファイルには、info.plistのようないくつかのファイルがあり、Manifest.dbという名前のデータベースとともに役立ちます。 次の表は、iTunesのバックアップのオペレーティングシステムによって異なるバックアップの場所を示しています-

OS Backup Location
Win7 C:\Users\[username]\AppData\Roaming\AppleComputer\MobileSync\Backup\
MAC OS X ~/Library/Application Suport/MobileSync/Backup/

PythonでiTunesバックアップを処理するには、まずオペレーティングシステムごとに、バックアップの場所にあるすべてのバックアップを識別する必要があります。 次に、各バックアップを反復処理し、データベースManifest.dbを読み取ります。

今、次のPythonコードの助けを借りて、同じことができます-

まず、次のように必要なライブラリをインポートします-

from __future__ import print_function
import argparse
import logging
import os

from shutil import copyfile
import sqlite3
import sys
logger = logging.getLogger(__name__)

今、2つの位置引数、すなわちiTunesバックアップと目的の出力フォルダを表すINPUT_DIRとOUTPUT_DIRを提供します-

if __name__ == "__main__":
   parser.add_argument("INPUT_DIR",help = "Location of folder containing iOS backups, ""e.g. ~\Library\Application Support\MobileSync\Backup folder")
   parser.add_argument("OUTPUT_DIR", help = "Output Directory")
   parser.add_argument("-l", help = "Log file path",default = __file__[:-2] + "log")
   parser.add_argument("-v", help = "Increase verbosity",action = "store_true") args = parser.parse_args()

今、次のようにログを設定します-

if args.v:
   logger.setLevel(logging.DEBUG)
else:
   logger.setLevel(logging.INFO)

今、次のようにこのログのメッセージ形式を設定します-

msg_fmt = logging.Formatter("%(asctime)-15s %(funcName)-13s""%(levelname)-8s %(message)s")
strhndl = logging.StreamHandler(sys.stderr)
strhndl.setFormatter(fmt = msg_fmt)

fhndl = logging.FileHandler(args.l, mode = 'a')
fhndl.setFormatter(fmt = msg_fmt)

logger.addHandler(strhndl)
logger.addHandler(fhndl)
logger.info("Starting iBackup Visualizer")
logger.debug("Supplied arguments: {}".format(" ".join(sys.argv[1:])))
logger.debug("System: " + sys.platform)
logger.debug("Python Version: " + sys.version)

次のコード行は、* os.makedirs()*関数を使用して、目的の出力ディレクトリに必要なフォルダーを作成します-

if not os.path.exists(args.OUTPUT_DIR):
   os.makedirs(args.OUTPUT_DIR)

今、次のようにmain()関数に提供された入力および出力ディレクトリを渡します-

if os.path.exists(args.INPUT_DIR) and os.path.isdir(args.INPUT_DIR):
   main(args.INPUT_DIR, args.OUTPUT_DIR)
else:
   logger.error("Supplied input directory does not exist or is not ""a directory")
   sys.exit(1)

さて、入力フォルダに存在するすべてのバックアップを識別するために* backup_summary()関数をさらに呼び出す main()*関数を記述します-

def main(in_dir, out_dir):
   backups = backup_summary(in_dir)
def backup_summary(in_dir):
   logger.info("Identifying all iOS backups in {}".format(in_dir))
   root = os.listdir(in_dir)
   backups = {}

   for x in root:
      temp_dir = os.path.join(in_dir, x)
      if os.path.isdir(temp_dir) and len(x) == 40:
         num_files = 0
         size = 0

         for root, subdir, files in os.walk(temp_dir):
            num_files += len(files)
            size += sum(os.path.getsize(os.path.join(root, name))
               for name in files)
         backups[x] = [temp_dir, num_files, size]
   return backups

今、次のようにコンソールに各バックアップの概要を印刷します-

print("Backup Summary")
print("=" *20)

if len(backups) > 0:
   for i, b in enumerate(backups):
      print("Backup No.: {} \n""Backup Dev. Name: {} \n""# Files: {} \n""Backup Size (Bytes): {}\n".format(i, b, backups[b][1], backups[b][2]))

次に、Manifest.dbファイルの内容をdb_itemsという名前の変数にダンプします。

try:
   db_items = process_manifest(backups[b][0])
   except IOError:
      logger.warn("Non-iOS 10 backup encountered or " "invalid backup. Continuing to next backup.")
continue

さて、バックアップのディレクトリパスを取る関数を定義しましょう-

def process_manifest(backup):
   manifest = os.path.join(backup, "Manifest.db")

   if not os.path.exists(manifest):
      logger.error("Manifest DB not found in {}".format(manifest))
      raise IOError

さて、SQLite3を使用して、cという名前のカーソルでデータベースに接続します-

c = conn.cursor()
items = {}

for row in c.execute("SELECT* from Files;"):
   items[row[0]] = [row[2], row[1], row[3]]
return items

create_files(in_dir, out_dir, b, db_items)
   print("=" * 20)
else:
   logger.warning("No valid backups found. The input directory should be
      " "the parent-directory immediately above the SHA-1 hash " "iOS device backups")
      sys.exit(2)

次に、次のように* create_files()*メソッドを定義します-

def create_files(in_dir, out_dir, b, db_items):
   msg = "Copying Files for backup {} to {}".format(b, os.path.join(out_dir, b))
   logger.info(msg)

ここで、 db_items 辞書の各キーを反復処理します-

for x, key in enumerate(db_items):
   if db_items[key][0] is None or db_items[key][0] == "":
      continue
   else:
      dirpath = os.path.join(out_dir, b,
os.path.dirname(db_items[key][0]))
   filepath = os.path.join(out_dir, b, db_items[key][0])

   if not os.path.exists(dirpath):
      os.makedirs(dirpath)
      original_dir = b + "/" + key[0:2] + "/" + key
   path = os.path.join(in_dir, original_dir)

   if os.path.exists(filepath):
      filepath = filepath + "_{}".format(x)

今、次のようにバックアップファイルをコピーするために* shutil.copyfile()*メソッドを使用します-

try:
   copyfile(path, filepath)
   except IOError:
      logger.debug("File not found in backup: {}".format(path))
         files_not_found += 1
   if files_not_found > 0:
      logger.warning("{} files listed in the Manifest.db not" "found in
backup".format(files_not_found))
   copyfile(os.path.join(in_dir, b, "Info.plist"), os.path.join(out_dir, b,
"Info.plist"))
   copyfile(os.path.join(in_dir, b, "Manifest.db"), os.path.join(out_dir, b,
"Manifest.db"))
   copyfile(os.path.join(in_dir, b, "Manifest.plist"), os.path.join(out_dir, b,
"Manifest.plist"))
   copyfile(os.path.join(in_dir, b, "Status.plist"),os.path.join(out_dir, b,
"Status.plist"))

上記のPythonスクリプトを使用すると、出力フォルダーに更新されたバックアップファイル構造を取得できます。 pycrypto pythonライブラリを使用して、バックアップを復号化できます。

Wi-Fi

モバイルデバイスは、どこでも利用可能なWi-Fiネットワークを介して接続することにより、外の世界に接続するために使用できます。 デバイスがこれらのオープンネットワークに自動的に接続される場合があります。

iPhoneの場合、デバイスが接続されたオープンWi-Fi接続のリストは、 com.apple.wifi.plist という名前のPLISTファイルに保存されます。 このファイルには、Wi-Fi SSID、BSSID、および接続時間が含まれます。

Pythonを使用して標準のCellebrite XMLレポートからWi-Fiの詳細を抽出する必要があります。 そのためには、Wi-Fiネットワークの名前を使用してデバイスの場所を見つけるのに使用できる一般的なプラットフォームである、Wireless Geographic Logging Engine(WIGLE)のAPIを使用する必要があります。

*requests* という名前のPythonライブラリを使用して、WIGLEからAPIにアクセスできます。 次のようにインストールできます-
pip install requests

WIGLEのAPI

WIGLEから無料のAPIを取得するには、WIGLEのウェブサイトhttps://wigle.net/accountに登録する必要があります。 WIGELのAPIを介してユーザーデバイスとその接続に関する情報を取得するためのPythonスクリプトについては、以下で説明します-

まず、さまざまなことを処理するために次のライブラリをインポートします-

from __future__ import print_function

import argparse
import csv
import os
import sys
import xml.etree.ElementTree as ET
import requests

次に、2つの位置引数、つまり INPUT_FILEOUTPUT_CSV を指定します。これらはそれぞれ、Wi-Fi MACアドレスを持つ入力ファイルと目的の出力CSVファイルを表します-

if __name__ == "__main__":
   parser.add_argument("INPUT_FILE", help = "INPUT FILE with MAC Addresses")
   parser.add_argument("OUTPUT_CSV", help = "Output CSV File")
   parser.add_argument("-t", help = "Input type: Cellebrite XML report or TXT
file",choices = ('xml', 'txt'), default = "xml")
   parser.add_argument('--api', help = "Path to API key
   file",default = os.path.expanduser("~/.wigle_api"),
   type = argparse.FileType('r'))
   args = parser.parse_args()

これで、次のコード行は、入力ファイルが存在し、ファイルであるかどうかを確認します。 そうでない場合は、スクリプトを終了します-

if not os.path.exists(args.INPUT_FILE) or \ not os.path.isfile(args.INPUT_FILE):
   print("[-] {} does not exist or is not a
file".format(args.INPUT_FILE))
   sys.exit(1)
directory = os.path.dirname(args.OUTPUT_CSV)
if directory != '' and not os.path.exists(directory):
   os.makedirs(directory)
api_key = args.api.readline().strip().split(":")

今、次のようにメインに引数を渡します-

main(args.INPUT_FILE, args.OUTPUT_CSV, args.t, api_key)
def main(in_file, out_csv, type, api_key):
   if type == 'xml':
      wifi = parse_xml(in_file)
   else:
      wifi = parse_txt(in_file)
query_wigle(wifi, out_csv, api_key)

今、私たちは次のようにXMLファイルを解析します-

def parse_xml(xml_file):
   wifi = {}
   xmlns = "{http://pa.cellebrite.com/report/2.0}"
   print("[+] Opening {} report".format(xml_file))

   xml_tree = ET.parse(xml_file)
   print("[+] Parsing report for all connected WiFi addresses")

   root = xml_tree.getroot()

今、次のようにルートの子要素を反復処理します-

for child in root.iter():
   if child.tag == xmlns + "model":
      if child.get("type") == "Location":
         for field in child.findall(xmlns + "field"):
            if field.get("name") == "TimeStamp":
               ts_value = field.find(xmlns + "value")
               try:
               ts = ts_value.text
               except AttributeError:
continue

次に、値のテキストに「ssid」文字列が存在するかどうかを確認します-

if "SSID" in value.text:
   bssid, ssid = value.text.split("\t")
   bssid = bssid[7:]
   ssid = ssid[6:]

今、私たちは次のように無線LAN辞書にBSSID、SSIDとタイムスタンプを追加する必要があります-

if bssid in wifi.keys():

wifi[bssid]["Timestamps"].append(ts)
   wifi[bssid]["SSID"].append(ssid)
else:
   wifi[bssid] = {"Timestamps": [ts], "SSID":
[ssid],"Wigle": {}}
return wifi

XMLパーサーよりもはるかに単純なテキストパーサーを以下に示します-

def parse_txt(txt_file):
   wifi = {}
   print("[+] Extracting MAC addresses from {}".format(txt_file))

   with open(txt_file) as mac_file:
      for line in mac_file:
         wifi[line.strip()] = {"Timestamps": ["N/A"], "SSID":
["N/A"],"Wigle": {}}
return wifi

さて、リクエストモジュールを使用して* WIGLE API 呼び出しを行い、 query_wigle()*メソッドに移動する必要があります-

def query_wigle(wifi_dictionary, out_csv, api_key):
   print("[+] Querying Wigle.net through Python API for {} "
"APs".format(len(wifi_dictionary)))
   for mac in wifi_dictionary:

   wigle_results = query_mac_addr(mac, api_key)
def query_mac_addr(mac_addr, api_key):

   query_url = "https://api.wigle.net/api/v2/network/search?" \
"onlymine = false&freenet = false&paynet = false" \ "&netid = {}".format(mac_addr)
   req = requests.get(query_url, auth = (api_key[0], api_key[1]))
   return req.json()

実際には、WIGLE APIコールには1日あたりの制限があります。その制限を超えると、次のようなエラーが表示されます。

try:
   if wigle_results["resultCount"] == 0:
      wifi_dictionary[mac]["Wigle"]["results"] = []
         continue
   else:
      wifi_dictionary[mac]["Wigle"] = wigle_results
except KeyError:
   if wigle_results["error"] == "too many queries today":
      print("[-] Wigle daily query limit exceeded")
      wifi_dictionary[mac]["Wigle"]["results"] = []
      continue
   else:
      print("[-] Other error encountered for " "address {}: {}".format(mac,
wigle_results['error']))
   wifi_dictionary[mac]["Wigle"]["results"] = []
   continue
prep_output(out_csv, wifi_dictionary)

ここで、* prep_output()*メソッドを使用して、辞書を簡単に書き込み可能なチャンクにフラット化します-

def prep_output(output, data):
   csv_data = {}
   google_map = https://www.google.com/maps/search/

さて、次のようにこれまでに収集したすべてのデータにアクセスします-

for x, mac in enumerate(data):
   for y, ts in enumerate(data[mac]["Timestamps"]):
      for z, result in enumerate(data[mac]["Wigle"]["results"]):
         shortres = data[mac]["Wigle"]["results"][z]
         g_map_url = "{}{},{}".format(google_map, shortres["trilat"],shortres["trilong"])

これで、* write_csv()*関数を使用して、この章の以前のスクリプトで行ったように、出力をCSVファイルに書き込むことができます。

埋め込みメタデータの調査

この章では、Pythonデジタルフォレンジックを使用した埋め込みメタデータの調査について詳しく学習します。

前書き

埋め込みメタデータは、同じファイルに格納されているデータに関する情報であり、そのデータによって記述されたオブジェクトを持っています。 つまり、デジタルファイル自体に保存されているデジタル資産に関する情報です。 常にファイルに関連付けられており、分離することはできません。

デジタルフォレンジックの場合、特定のファイルに関するすべての情報を抽出することはできません。 一方、埋め込みメタデータは、調査に重要な情報を提供します。 たとえば、テキストファイルのメタデータには、作成者に関する情報、その長さ、作成日、およびそのドキュメントに関する短い要約が含まれる場合があります。 デジタル画像には、画像の長さ、シャッター速度などのメタデータが含まれる場合があります。

メタデータ属性を含むアーティファクトとその抽出

このセクションでは、メタデータ属性を含むさまざまなアーティファクトとPythonを使用した抽出プロセスについて学習します。

オーディオとビデオ

これらは、メタデータが埋め込まれた2つの非常に一般的なアーティファクトです。 このメタデータは、調査目的で抽出できます。

次のPythonスクリプトを使用して、オーディオまたはMP3ファイルとビデオまたはMP4ファイルから共通の属性またはメタデータを抽出できます。

このスクリプトには、mutagenという名前のサードパーティのPythonライブラリをインストールする必要があります。これにより、オーディオファイルとビデオファイルからメタデータを抽出できるようになります。 それは、次のコマンドの助けを借りてインストールすることができます-

pip install mutagen

このPythonスクリプトのためにインポートする必要がある便利なライブラリのいくつかは次のとおりです-

from __future__ import print_function

import argparse
import json
import mutagen

コマンドラインハンドラーは、MP3またはMP4ファイルへのパスを表す1つの引数を取ります。 その後、次のようにファイルへのハンドルを開くために* mutagen.file()*メソッドを使用します-

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Python Metadata Extractor')
   parser.add_argument("AV_FILE", help="File to extract metadata from")
   args = parser.parse_args()
   av_file = mutagen.File(args.AV_FILE)
   file_ext = args.AV_FILE.rsplit('.', 1)[-1]

   if file_ext.lower() == 'mp3':
      handle_id3(av_file)
   elif file_ext.lower() == 'mp4':
      handle_mp4(av_file)

次に、2つのハンドルを使用する必要があります。1つはMP3からデータを抽出し、もう1つはMP4ファイルからデータを抽出します。 次のようにこれらのハンドルを定義できます-

def handle_id3(id3_file):
   id3_frames = {'TIT2': 'Title', 'TPE1': 'Artist', 'TALB': 'Album','TXXX':
      'Custom', 'TCON': 'Content Type', 'TDRL': 'Date released','COMM': 'Comments',
         'TDRC': 'Recording Date'}
   print("{:15} | {:15} | {:38} | {}".format("Frame", "Description","Text","Value"))
   print("-" *85)

   for frames in id3_file.tags.values():
      frame_name = id3_frames.get(frames.FrameID, frames.FrameID)
      desc = getattr(frames, 'desc', "N/A")
      text = getattr(frames, 'text', ["N/A"])[0]
      value = getattr(frames, 'value', "N/A")

      if "date" in frame_name.lower():
         text = str(text)
      print("{:15} | {:15} | {:38} | {}".format(
         frame_name, desc, text, value))
def handle_mp4(mp4_file):
   cp_sym = u"\u00A9"
   qt_tag = {
      cp_sym + 'nam': 'Title', cp_sym + 'art': 'Artist',
      cp_sym + 'alb': 'Album', cp_sym + 'gen': 'Genre',
      'cpil': 'Compilation', cp_sym + 'day': 'Creation Date',
      'cnID': 'Apple Store Content ID', 'atID': 'Album Title ID',
      'plID': 'Playlist ID', 'geID': 'Genre ID', 'pcst': 'Podcast',
      'purl': 'Podcast URL', 'egid': 'Episode Global ID',
      'cmID': 'Camera ID', 'sfID': 'Apple Store Country',
      'desc': 'Description', 'ldes': 'Long Description'}
genre_ids = json.load(open('apple_genres.json'))

今、私たちは次のようにこのMP4ファイルを反復処理する必要があります-

print("{:22} | {}".format('Name', 'Value'))
print("-"* 40)

for name, value in mp4_file.tags.items():
   tag_name = qt_tag.get(name, name)

   if isinstance(value, list):
      value = "; ".join([str(x) for x in value])
   if name == 'geID':
      value = "{}: {}".format(
      value, genre_ids[str(value)].replace("|", " - "))
   print("{:22} | {}".format(tag_name, value))

上記のスクリプトは、MP3ファイルとMP4ファイルに関する追加情報を提供します。

画像

画像には、ファイル形式に応じて異なる種類のメタデータが含まれる場合があります。 ただし、ほとんどの画像にはGPS情報が埋め込まれています。 サードパーティのPythonライブラリを使用して、このGPS情報を抽出できます。 あなたは同じことをするために使用できる次のPythonスクリプトを使用することができます-

まず、次のように* Python Imaging Library(PIL)*という名前のサードパーティのPythonライブラリをダウンロードします-

pip install pillow

これは、画像からメタデータを抽出するのに役立ちます。

また、画像に埋め込まれたGPSの詳細をKMLファイルに書き込むこともできますが、このために simplekml という名前のサードパーティPythonライブラリを次のようにダウンロードする必要があります-

pip install simplekml

このスクリプトでは、最初に次のライブラリをインポートする必要があります-

from __future__ import print_function
import argparse

from PIL import Image
from PIL.ExifTags import TAGS

import simplekml
import sys

これで、コマンドラインハンドラーは、基本的に写真のファイルパスを表す1つの位置引数を受け入れます。

parser = argparse.ArgumentParser('Metadata from images')
parser.add_argument('PICTURE_FILE', help = "Path to picture")
args = parser.parse_args()

次に、座標情報を取り込むURLを指定する必要があります。 URLは gmaps および open_maps です。 また、PILライブラリによって提供される度分秒(DMS)タプル座標を10進数に変換する関数も必要です。 それは次のように行うことができます-

gmaps = "https://www.google.com/maps?q={},{}"
open_maps = "http://www.openstreetmap.org/?mlat={}&mlon={}"

def process_coords(coord):
   coord_deg = 0

   for count, values in enumerate(coord):
      coord_deg += (float(values[0])/values[1])/60**count
   return coord_deg

ここで、* image.open()*関数を使用して、ファイルをPILオブジェクトとして開きます。

img_file = Image.open(args.PICTURE_FILE)
exif_data = img_file._getexif()

if exif_data is None:
   print("No EXIF data found")
   sys.exit()
for name, value in exif_data.items():
   gps_tag = TAGS.get(name, name)
   if gps_tag is not 'GPSInfo':
      continue
*GPSInfo* タグを見つけたら、GPS参照を保存し、* process_coords()*メソッドで座標を処理します。
lat_ref = value[1] == u'N'
lat = process_coords(value[2])

if not lat_ref:
   lat = lat *-1
lon_ref = value[3] == u'E'
lon = process_coords(value[4])

if not lon_ref:
   lon = lon* -1

さて、次のように simplekml ライブラリから kml オブジェクトを開始します-

kml = simplekml.Kml()
kml.newpoint(name = args.PICTURE_FILE, coords = [(lon, lat)])
kml.save(args.PICTURE_FILE + ".kml")

次のように処理された情報から座標を印刷できるようになりました-

print("GPS Coordinates: {}, {}".format(lat, lon))
print("Google Maps URL: {}".format(gmaps.format(lat, lon)))
print("OpenStreetMap URL: {}".format(open_maps.format(lat, lon)))
print("KML File {} created".format(args.PICTURE_FILE + ".kml"))

PDFドキュメント

PDFドキュメントには、画像、テキスト、フォームなど、さまざまなメディアがあります。 PDFドキュメントに埋め込まれたメタデータを抽出すると、結果データをExtensible Metadata Platform(XMP)と呼ばれる形式で取得する場合があります。 私たちは次のPythonコードの助けを借りてメタデータを抽出できます-

最初に、 PyPDF2 という名前のサードパーティPythonライブラリをインストールして、XMP形式で保存されたメタデータを読み取ります。 次のようにインストールできます-

pip install PyPDF2

今、PDFファイルからメタデータを抽出するために次のライブラリをインポートします-

from __future__ import print_function
from argparse import ArgumentParser, FileType

import datetime
from PyPDF2 import PdfFileReader
import sys

これで、コマンドラインハンドラーは、基本的にPDFファイルのファイルパスを表す1つの位置引数を受け入れます。

parser = argparse.ArgumentParser('Metadata from PDF')
parser.add_argument('PDF_FILE', help='Path to PDF file',type=FileType('rb'))
args = parser.parse_args()

今、我々は次のように利用可能なメタデータを含むオブジェクトを提供するために* getXmpMetadata()*メソッドを使用することができます-

pdf_file = PdfFileReader(args.PDF_FILE)
xmpm = pdf_file.getXmpMetadata()

if xmpm is None:
   print("No XMP metadata found in document.")
   sys.exit()
  • custom_print()*メソッドを使用して、title、creator、contributorなどの関連する値を抽出および印刷できます。 次のように-
custom_print("Title: {}", xmpm.dc_title)
custom_print("Creator(s): {}", xmpm.dc_creator)
custom_print("Contributors: {}", xmpm.dc_contributor)
custom_print("Subject: {}", xmpm.dc_subject)
custom_print("Description: {}", xmpm.dc_description)
custom_print("Created: {}", xmpm.xmp_createDate)
custom_print("Modified: {}", xmpm.xmp_modifyDate)
custom_print("Event Dates: {}", xmpm.dc_date)

また、次のように複数のソフトウェアを使用してPDFを作成する場合に* custom_print()*メソッドを定義することもできます-

def custom_print(fmt_str, value):
   if isinstance(value, list):
      print(fmt_str.format(", ".join(value)))
   elif isinstance(value, dict):
      fmt_value = [":".join((k, v)) for k, v in value.items()]
      print(fmt_str.format(", ".join(value)))
   elif isinstance(value, str) or isinstance(value, bool):
      print(fmt_str.format(value))
   elif isinstance(value, bytes):
      print(fmt_str.format(value.decode()))
   elif isinstance(value, datetime.datetime):
      print(fmt_str.format(value.isoformat()))
   elif value is None:
      print(fmt_str.format("N/A"))
   else:
      print("warn: unhandled type {} found".format(type(value)))

また、次のようにソフトウェアによって保存された他のカスタムプロパティを抽出することができます-

if xmpm.custom_properties:
   print("Custom Properties:")

   for k, v in xmpm.custom_properties.items():
      print("\t{}: {}".format(k, v))

上記のスクリプトはPDFドキュメントを読み取り、そのPDFが作成されたソフトウェアを使用して保存されたカスタムプロパティを含むXMP形式で保存されたメタデータを印刷します。

Windows実行可能ファイル

疑わしい、または不正な実行可能ファイルに遭遇する場合があります。 ただし、調査のためには、メタデータが埋め込まれているため便利です。 その場所、目的、製造業者、編集日などのその他の属性などの情報を取得できます。 次のPythonスクリプトの助けを借りて、コンパイル日、ヘッダーから有用なデータ、インポートされたシンボル、エクスポートされたシンボルを取得できます。

この目的のために、最初にサードパーティのPythonライブラリ pefile をインストールします。 それは次のように行うことができます-

pip install pefile

これを正常にインストールしたら、次のように次のライブラリをインポートします-

from __future__ import print_function

import argparse
from datetime import datetime
from pefile import PE

これで、コマンドラインハンドラは、基本的に実行可能ファイルのファイルパスを表す1つの位置引数を受け入れます。 また、詳細で詳細な方法または単純化された方法で必要な場合でも、出力のスタイルを選択できます。 このためには、以下に示すようにオプションの引数を与える必要があります-

parser = argparse.ArgumentParser('Metadata from executable file')
parser.add_argument("EXE_FILE", help = "Path to exe file")
parser.add_argument("-v", "--verbose", help = "Increase verbosity of output",
action = 'store_true', default = False)
args = parser.parse_args()

次に、PEクラスを使用して入力実行可能ファイルをロードします。 また、* dump_dict()*メソッドを使用して、実行可能データを辞書オブジェクトにダンプします。

pe = PE(args.EXE_FILE)
ped = pe.dump_dict()

以下に示すコードを使用して、埋め込みオーサーシップ、バージョン、コンパイル時間などの基本的なファイルメタデータを抽出できます-

file_info = {}
for structure in pe.FileInfo:
   if structure.Key == b'StringFileInfo':
      for s_table in structure.StringTable:
         for key, value in s_table.entries.items():
            if value is None or len(value) == 0:
               value = "Unknown"
            file_info[key] = value
print("File Information: ")
print("==================")

for k, v in file_info.items():
   if isinstance(k, bytes):
      k = k.decode()
   if isinstance(v, bytes):
      v = v.decode()
   print("{}: {}".format(k, v))
comp_time = ped['FILE_HEADER']['TimeDateStamp']['Value']
comp_time = comp_time.split("[")[-1].strip("]")
time_stamp, timezone = comp_time.rsplit(" ", 1)
comp_time = datetime.strptime(time_stamp, "%a %b %d %H:%M:%S %Y")
print("Compiled on {} {}".format(comp_time, timezone.strip()))

次のようにヘッダーから有用なデータを抽出できます-

for section in ped['PE Sections']:
   print("Section '{}' at {}: {}/{} {}".format(
      section['Name']['Value'], hex(section['VirtualAddress']['Value']),
      section['Misc_VirtualSize']['Value'],
      section['SizeOfRawData']['Value'], section['MD5'])
   )

次に、以下に示すように、実行可能ファイルからインポートおよびエクスポートのリストを抽出します-

if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
   print("\nImports: ")
   print("=========")

   for dir_entry in pe.DIRECTORY_ENTRY_IMPORT:
      dll = dir_entry.dll

      if not args.verbose:
         print(dll.decode(), end=", ")
         continue
      name_list = []

      for impts in dir_entry.imports:
         if getattr(impts, "name", b"Unknown") is None:
            name = b"Unknown"
         else:
            name = getattr(impts, "name", b"Unknown")
            name_list.append([name.decode(), hex(impts.address)])
      name_fmt = ["{} ({})".format(x[0], x[1]) for x in name_list]
      print('- {}: {}'.format(dll.decode(), ", ".join(name_fmt)))
   if not args.verbose:
      print()

次に、以下に示すコードを使用して、 exportsnames 、および addresses を印刷します-

if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):
   print("\nExports: ")
   print("=========")

   for sym in pe.DIRECTORY_ENTRY_EXPORT.symbols:
      print('- {}: {}'.format(sym.name.decode(), hex(sym.address)))

上記のスクリプトは、基本的なメタデータ、Windows実行可能ファイルのヘッダーからの情報を抽出します。

Officeドキュメントのメタデータ

コンピューターでの作業のほとんどは、MS Officeの3つのアプリケーション(Word、PowerPoint、Excel)で行われます。 これらのファイルには膨大なメタデータが含まれており、そのメタデータにより、作成者や履歴に関する興味深い情報が公開されます。

2007形式のword(.docx)、excel(.xlsx)、およびpowerpoint(.pptx)のメタデータはXMLファイルに保存されていることに注意してください。 以下に示すPythonスクリプトを使用して、これらのXMLファイルをPythonで処理できます-

まず、以下に示すように必要なライブラリをインポートします-

from __future__ import print_function
from argparse import ArgumentParser
from datetime import datetime as dt
from xml.etree import ElementTree as etree

import zipfile
parser = argparse.ArgumentParser('Office Document Metadata’)
parser.add_argument("Office_File", help="Path to office file to read")
args = parser.parse_args()

次に、ファイルがZIPファイルかどうかを確認します。 それ以外の場合は、エラーが発生します。 今、ファイルを開き、次のコードを使用して処理するための重要な要素を抽出します-

zipfile.is_zipfile(args.Office_File)
zfile = zipfile.ZipFile(args.Office_File)
core_xml = etree.fromstring(zfile.read('docProps/core.xml'))
app_xml = etree.fromstring(zfile.read('docProps/app.xml'))

今、メタデータの抽出を開始するための辞書を作成します-

core_mapping = {
   'title': 'Title',
   'subject': 'Subject',
   'creator': 'Author(s)',
   'keywords': 'Keywords',
   'description': 'Description',
   'lastModifiedBy': 'Last Modified By',
   'modified': 'Modified Date',
   'created': 'Created Date',
   'category': 'Category',
   'contentStatus': 'Status',
   'revision': 'Revision'
}
  • iterchildren()*メソッドを使用して、XMLファイル内の各タグにアクセスします-
for element in core_xml.getchildren():
   for key, title in core_mapping.items():
      if key in element.tag:
         if 'date' in title.lower():
            text = dt.strptime(element.text, "%Y-%m-%dT%H:%M:%SZ")
         else:
            text = element.text
         print("{}: {}".format(title, text))

同様に、ドキュメントの内容に関する統計情報を含むapp.xmlファイルに対してこれを行います-

app_mapping = {
   'TotalTime': 'Edit Time (minutes)',
   'Pages': 'Page Count',
   'Words': 'Word Count',
   'Characters': 'Character Count',
   'Lines': 'Line Count',
   'Paragraphs': 'Paragraph Count',
   'Company': 'Company',
   'HyperlinkBase': 'Hyperlink Base',
   'Slides': 'Slide count',
   'Notes': 'Note Count',
   'HiddenSlides': 'Hidden Slide Count',
}
for element in app_xml.getchildren():
   for key, title in app_mapping.items():
      if key in element.tag:
         if 'date' in title.lower():
            text = dt.strptime(element.text, "%Y-%m-%dT%H:%M:%SZ")
         else:
            text = element.text
         print("{}: {}".format(title, text))

上記のスクリプトを実行した後、特定のドキュメントに関するさまざまな詳細を取得できます。 このスクリプトは、Office 2007以降のバージョンのドキュメントにのみ適用できることに注意してください。

Pythonデジタルネットワークフォレンジック-I

この章では、Pythonを使用してネットワークフォレンジックを実行する際の基礎について説明します。

ネットワークフォレンジックについて

ネットワークフォレンジックは、情報収集、証拠収集、または侵入検知を目的として、ローカルおよびWAN(ワイドエリアネットワーク)の両方のコンピューターネットワークトラフィックの監視と分析を扱うデジタルフォレンジックのブランチです。 ネットワークフォレンジックは、知的財産の盗難や情報漏洩などのデジタル犯罪の調査に重要な役割を果たします。 ネットワーク通信の写真は、次のように調査員がいくつかの重要な問題を解決するのに役立ちます-

  • どのWebサイトにアクセスしましたか?
  • ネットワークにはどのようなコンテンツがアップロードされていますか?
  • ネットワークからどのようなコンテンツがダウンロードされましたか?
  • どのサーバーにアクセスしていますか?
  • 誰かが会社のファイアウォールの外に機密情報を送信していますか?

インターネットエビデンスファインダー(IEF)

IEFは、コンピューター、スマートフォン、タブレットなどのさまざまなデジタルメディアで見つかったデジタル証拠を検索、分析、および提示するためのデジタルフォレンジックツールです。 これは非常に人気があり、数千人の法医学専門家によって使用されています。

IEFの使用

IEFは人気があるため、科学捜査の専門家によってかなり使用されています。 IEFの用途のいくつかは次のとおりです-

  • その強力な検索機能により、複数のファイルまたはデータメディアを同時に検索するために使用されます。
  • また、新しいカービング手法により、RAMの未割り当て領域から削除されたデータを回復するためにも使用されます。
  • 調査者が、開かれた日に元の形式でWebページを再構築する場合は、IEFを使用できます。
  • また、論理ディスクボリュームまたは物理ディスクボリュームの検索にも使用されます。

Pythonを使用してIEFからCSVにレポートをダンプする

IEFはSQLiteデータベースにデータを保存し、次のPythonスクリプトはIEFデータベース内の結果テーブルを動的に識別し、それぞれのCSVファイルにダンプします。

このプロセスは、以下に示す手順で実行されます

  • まず、拡張子が.dbのSQLiteデータベースファイルであるIEF結果データベースを生成します。
  • 次に、そのデータベースを照会して、すべてのテーブルを識別します。 *最後に、この結果テーブルを個々のCSVファイルに書き込みます。

Pythonコード

この目的のためにPythonコードを使用する方法を見てみましょう-

Pythonスクリプトの場合、次のように必要なライブラリをインポートします-

from __future__ import print_function

import argparse
import csv
import os
import sqlite3
import sys

今、私たちはIEFデータベースファイルへのパスを提供する必要があります-

if __name__ == '__main__':
   parser = argparse.ArgumentParser('IEF to CSV')
   parser.add_argument("IEF_DATABASE", help="Input IEF database")
   parser.add_argument("OUTPUT_DIR", help="Output DIR")
   args = parser.parse_args()

今、私たちは次のようにIEFデータベースの存在を確認します-

if not os.path.exists(args.OUTPUT_DIR):
   os.makedirs(args.OUTPUT_DIR)
if os.path.exists(args.IEF_DATABASE) and \ os.path.isfile(args.IEF_DATABASE):
   main(args.IEF_DATABASE, args.OUTPUT_DIR)
else:
   print("[-] Supplied input file {} does not exist or is not a " "file".format(args.IEF_DATABASE))
   sys.exit(1)

さて、以前のスクリプトで行ったように、カーソルを介してクエリを実行するには、次のようにSQLiteデータベースと接続します-

def main(database, out_directory):
   print("[+] Connecting to SQLite database")
   conn = sqlite3.connect(database)
   c = conn.cursor()

次のコード行は、データベースからテーブルの名前を取得します-

print("List of all tables to extract")
c.execute("select* from sqlite_master where type = 'table'")
tables = [x[2] for x in c.fetchall() if not x[2].startswith('_') and not x[2].endswith('_DATA')]

次に、テーブルからすべてのデータを選択し、カーソルオブジェクトで* fetchall()*メソッドを使用して、テーブルのデータ全体を含むタプルのリストを変数に保存します-

print("Dumping {} tables to CSV files in {}".format(len(tables), out_directory))

for table in tables:
c.execute("pragma table_info('{}')".format(table))
table_columns = [x[1] for x in c.fetchall()]

c.execute("select * from '{}'".format(table))
table_data = c.fetchall()

今、* CSV_Writer()*メソッドを使用して、CSVファイルにコンテンツを書き込みます-

csv_name = table + '.csv'
csv_path = os.path.join(out_directory, csv_name)
print('[+] Writing {} table to {} CSV file'.format(table,csv_name))

with open(csv_path, "w", newline = "") as csvfile:
   csv_writer = csv.writer(csvfile)
   csv_writer.writerow(table_columns)
   csv_writer.writerows(table_data)

上記のスクリプトは、IEFデータベースのテーブルからすべてのデータを取得し、選択したCSVファイルに内容を書き込みます。

キャッシュデータの操作

IEF結果データベースから、IEF自体で必ずしもサポートされていない情報を取得できます。 ヤフー、グーグルなどの電子メールサービスプロバイダーから、キャッシュされたデータ、情報のバイプロダクトを取得できます。 IEF結果データベースを使用します。

以下は、IEFデータベースを使用して、Google ChromeでアクセスされるYahooメールからキャッシュされたデータ情報にアクセスするためのPythonスクリプトです。 手順は、最後のPythonスクリプトの手順とほぼ同じであることに注意してください。

まず、次のようにPythonに必要なライブラリをインポートします-

from __future__ import print_function
import argparse
import csv
import os
import sqlite3
import sys
import json

ここで、最後のスクリプトで行われたように、コマンドラインハンドラーが受け入れる2つの位置引数とともにIEFデータベースファイルへのパスを指定します-

if __name__ == '__main__':
   parser = argparse.ArgumentParser('IEF to CSV')
   parser.add_argument("IEF_DATABASE", help="Input IEF database")
   parser.add_argument("OUTPUT_DIR", help="Output DIR")
   args = parser.parse_args()

今、次のようにIEFデータベースの存在を確認します-

directory = os.path.dirname(args.OUTPUT_CSV)

if not os.path.exists(directory):os.makedirs(directory)
if os.path.exists(args.IEF_DATABASE) and \ os.path.isfile(args.IEF_DATABASE):
   main(args.IEF_DATABASE, args.OUTPUT_CSV)
   else: print("Supplied input file {} does not exist or is not a " "file".format(args.IEF_DATABASE))
sys.exit(1)

今、カーソルを介してクエリを実行するために、次のようにSQLiteデータベースとの接続を行います-

def main(database, out_csv):
   print("[+] Connecting to SQLite database")
   conn = sqlite3.connect(database)
   c = conn.cursor()

次のコード行を使用して、Yahoo Mailの連絡先キャッシュレコードのインスタンスを取得できます-

print("Querying IEF database for Yahoo Contact Fragments from " "the Chrome Cache Records Table")
   try:
      c.execute("select * from 'Chrome Cache Records' where URL like " "'https://data.mail.yahoo.com" "/classicab/v2/contacts/?format=json%'")
   except sqlite3.OperationalError:
      print("Received an error querying the database --    database may be" "corrupt or not have a Chrome Cache Records table")
      sys.exit(2)

さて、次のように変数に保存される上記のクエリから返されたタプルのリスト-

contact_cache = c.fetchall()
contact_data = process_contacts(contact_cache)
write_csv(contact_data, out_csv)

ここでは、結果リストを設定するために* process_contacts()を使用するだけでなく、各連絡先キャッシュレコードを繰り返し処理し、 json.loads()*を使用してテーブルから抽出したJSONデータをさらに操作-

def process_contacts(contact_cache):
   print("[+] Processing {} cache files matching Yahoo contact cache " " data".format(len(contact_cache)))
   results = []

   for contact in contact_cache:
      url = contact[0]
      first_visit = contact[1]
      last_visit = contact[2]
      last_sync = contact[3]
      loc = contact[8]
       contact_json = json.loads(contact[7].decode())
      total_contacts = contact_json["total"]
      total_count = contact_json["count"]

      if "contacts" not in contact_json:
         continue
      for c in contact_json["contacts"]:
         name, anni, bday, emails, phones, links = ("", "", "", "", "", "")
            if "name" in c:
            name = c["name"]["givenName"] + " " + \ c["name"]["middleName"] + " " + c["name"]["familyName"]

            if "anniversary" in c:
            anni = c["anniversary"]["month"] + \"/" + c["anniversary"]["day"] + "/" + \c["anniversary"]["year"]

            if "birthday" in c:
            bday = c["birthday"]["month"] + "/" + \c["birthday"]["day"] + "/" + c["birthday"]["year"]

            if "emails" in c:
               emails = ', '.join([x["ep"] for x in c["emails"]])

            if "phones" in c:
               phones = ', '.join([x["ep"] for x in c["phones"]])

            if "links" in c:
              links = ', '.join([x["ep"] for x in c["links"]])

今、会社、タイトル、メモについては、以下に示すようにgetメソッドが使用されます-

company = c.get("company", "")
title = c.get("jobTitle", "")
notes = c.get("notes", "")

今、次のように結果リストにメタデータと抽出されたデータ要素のリストを追加しましょう-

results.append([url, first_visit, last_visit, last_sync, loc, name, bday,anni, emails, phones, links, company, title, notes,total_contacts, total_count])
return results

今、* CSV_Writer()*メソッドを使用して、CSVファイルにコンテンツを書き込みます-

def write_csv(data, output):
   print("[+] Writing {} contacts to {}".format(len(data), output))
   with open(output, "w", newline="") as csvfile:
      csv_writer = csv.writer(csvfile)
      csv_writer.writerow([
         "URL", "First Visit (UTC)", "Last Visit (UTC)",
         "Last Sync (UTC)", "Location", "Contact Name", "Bday",
         "Anniversary", "Emails", "Phones", "Links", "Company", "Title",
         "Notes", "Total Contacts", "Count of Contacts in Cache"])
      csv_writer.writerows(data)

上記のスクリプトの助けを借りて、IEFデータベースを使用して、Yahooメールからキャッシュされたデータを処理できます。

Pythonデジタルネットワークフォレンジック-II

前の章では、Pythonを使用したネットワークフォレンジックの概念のいくつかを扱いました。 この章では、Pythonを使用したネットワークフォレンジックをより深いレベルで理解しましょう。

美しいスープでWebページを保存

World Wide Web(WWW)は、独自の情報リソースです。 ただし、驚異的な速度でコンテンツが失われるため、そのレガシーはリスクが高くなります。 多くの文化遺産および学術機関、非営利団体、民間企業が、関連する問題を調査し、Webアーカイブの技術的ソリューションの開発に貢献してきました。

Webページの保存またはWebアーカイブは、World Wide Webからデータを収集し、データをアーカイブに保存し、将来の研究者、歴史家、一般の人々が利用できるようにするプロセスです。 ウェブページの保存にさらに進む前に、以下に示すように、ウェブページの保存に関連するいくつかの重要な問題を議論しましょう-

  • * Webリソースの変更*-Webリソースは日々変化し続けており、これはWebページの保存の課題です。
  • 大量のリソース-Webページの保存に関連する別の問題は、大量のリソースが保存されることです。
  • 整合性-Webページは、その完全性を保護するために、不正な修正、削除、または削除から保護する必要があります。
  • マルチメディアデータの取り扱い-Webページを保存している間、マルチメディアデータも処理する必要があり、これらを行うと問題が発生する可能性があります。
  • アクセスの提供-保存のほかに、Webリソースへのアクセスを提供し、所有権の問題に対処する問題も解決する必要があります。

この章では、Webページの保存に Beautiful Soup という名前のPythonライブラリを使用します。

美しいスープとは?

Beautiful Soupは、HTMLおよびXMLファイルからデータを引き出すためのPythonライブラリです。 Webページ自体を取得できないため、スープオブジェクトを作成するには入力(ドキュメントまたはURL)が必要なため、 urlib で使用できます。 これについて詳しくはhttps://www.crummy.com/software/BeautifulSoup/bs4/doc/.[www.crummy.com/software/BeautifulSoup/bs4/doc/]で学ぶことができます。

それを使用する前に、次のコマンドを使用してサードパーティのライブラリをインストールする必要があることに注意してください-

pip install bs4

次に、Anacondaパッケージマネージャーを使用して、Beautiful Soupを次のようにインストールできます-

conda install -c anaconda beautifulsoup4

Webページを保持するためのPythonスクリプト

Beautiful Soupと呼ばれるサードパーティのライブラリを使用してWebページを保存するためのPythonスクリプトはここで議論されています-

まず、次のように必要なライブラリをインポートします-

from __future__ import print_function
import argparse

from bs4 import BeautifulSoup, SoupStrainer
from datetime import datetime

import hashlib
import logging
import os
import ssl
import sys
from urllib.request import urlopen

import urllib.error
logger = logging.getLogger(__name__)

このスクリプトは2つの位置引数を取ることに注意してください。1つは保存されるURLであり、もう1つは以下に示すように望ましい出力ディレクトリです-

if __name__ == "__main__":
   parser = argparse.ArgumentParser('Web Page preservation')
   parser.add_argument("DOMAIN", help="Website Domain")
   parser.add_argument("OUTPUT_DIR", help="Preservation Output Directory")
   parser.add_argument("-l", help="Log file path",
   default=__file__[:-3] + ".log")
   args = parser.parse_args()

さて、ループにあるファイルとストリームハンドラを指定して、スクリプトのログを設定し、示されているように取得プロセスを文書化します-

logger.setLevel(logging.DEBUG)
msg_fmt = logging.Formatter("%(asctime)-15s %(funcName)-10s""%(levelname)-8s %(message)s")
strhndl = logging.StreamHandler(sys.stderr)
strhndl.setFormatter(fmt=msg_fmt)
fhndl = logging.FileHandler(args.l, mode='a')
fhndl.setFormatter(fmt=msg_fmt)

logger.addHandler(strhndl)
logger.addHandler(fhndl)
logger.info("Starting BS Preservation")
logger.debug("Supplied arguments: {}".format(sys.argv[1:]))
logger.debug("System " + sys.platform)
logger.debug("Version " + sys.version)

さて、次のように目的の出力ディレクトリで入力検証を行いましょう-

if not os.path.exists(args.OUTPUT_DIR):
   os.makedirs(args.OUTPUT_DIR)
main(args.DOMAIN, args.OUTPUT_DIR)

ここで、次のように入力URLの追加の検証とともに実際の名前の前に不要な要素を削除することにより、Webサイトのベース名を抽出する* main()*関数を定義します-

def main(website, output_dir):
   base_name = website.replace("https://", "").replace("http://", "").replace("www.", "")
   link_queue = set()

   if "http://" not in website and "https://" not in website:
      logger.error("Exiting preservation - invalid user input: {}".format(website))
      sys.exit(1)
   logger.info("Accessing {} webpage".format(website))
   context = ssl._create_unverified_context()

次に、urlopen()メソッドを使用して、URLとの接続を開く必要があります。 次のようにtry-exceptブロックを使用してみましょう-

try:
   index = urlopen(website, context=context).read().decode("utf-8")
except urllib.error.HTTPError as e:
   logger.error("Exiting preservation - unable to access page: {}".format(website))
   sys.exit(2)
logger.debug("Successfully accessed {}".format(website))

コードの次の行には、以下で説明する3つの関数が含まれます-

  • * write_output()*は、最初のWebページを出力ディレクトリに書き込みます
  • このWebページ上のリンクを識別する* find_links()*関数
  • * recurse_pages()*関数は、Webページ上のすべてのリンクを反復して検出します。
write_output(website, index, output_dir)
link_queue = find_links(base_name, index, link_queue)
logger.info("Found {} initial links on webpage".format(len(link_queue)))
recurse_pages(website, link_queue, context, output_dir)
logger.info("Completed preservation of {}".format(website))

さて、次のように* write_output()*メソッドを定義しましょう-

def write_output(name, data, output_dir, counter=0):
   name = name.replace("http://", "").replace("https://", "").rstrip("//")
   directory = os.path.join(output_dir, os.path.dirname(name))

   if not os.path.exists(directory) and os.path.dirname(name) != "":
      os.makedirs(directory)

私たちは、Webページに関するいくつかの詳細を記録する必要があり、その後、次のように* hash_data()*メソッドを使用してデータのハッシュを記録します-

logger.debug("Writing {} to {}".format(name, output_dir)) logger.debug("Data Hash: {}".format(hash_data(data)))
path = os.path.join(output_dir, name)
path = path + "_" + str(counter)
with open(path, "w") as outfile:
   outfile.write(data)
logger.debug("Output File Hash: {}".format(hash_file(path)))

今、私たちは UTF-8 エンコードされたデータを読み取り、次のようにその SHA-256 ハッシュを生成する助けを借りて* hash_data()*メソッドを定義します-

def hash_data(data):
   sha256 = hashlib.sha256()
   sha256.update(data.encode("utf-8"))
   return sha256.hexdigest()
def hash_file(file):
   sha256 = hashlib.sha256()
   with open(file, "rb") as in_file:
      sha256.update(in_file.read())
return sha256.hexdigest()

さて、次のように* find_links()メソッドの下でWebページのデータから *Beautifulsoup オブジェクトを作成しましょう-

def find_links(website, page, queue):
   for link in BeautifulSoup(page, "html.parser",parse_only = SoupStrainer("a", href = True)):
      if website in link.get("href"):
         if not os.path.basename(link.get("href")).startswith("#"):
            queue.add(link.get("href"))
   return queue

今、私たちは次のようにウェブサイトのURL、現在のリンクキュー、未検証のSSLコンテキストと出力ディレクトリの入力を提供することにより、* recurse_pages()*メソッドを定義する必要があります-

def recurse_pages(website, queue, context, output_dir):
   processed = []
   counter = 0

   while True:
      counter += 1
      if len(processed) == len(queue):
         break
      for link in queue.copy(): if link in processed:
         continue
       processed.append(link)
      try:
      page = urlopen(link,      context=context).read().decode("utf-8")
      except urllib.error.HTTPError as e:
         msg = "Error accessing webpage: {}".format(link)
         logger.error(msg)
         continue

さて、次のようにリンク名、ページデータ、出力ディレクトリとカウンターを渡すことにより、ファイル内のアクセスされた各Webページの出力を書きます-

write_output(link, page, output_dir, counter)
queue = find_links(website, page, queue)
logger.info("Identified {} links throughout website".format(
   len(queue)))

これで、WebサイトのURL、出力ディレクトリ、およびログファイルへのパスを指定してこのスクリプトを実行すると、将来の使用に使用できるWebページに関する詳細が取得されます。

ウイルスハンティング

法医学アナリスト、セキュリティ研究者、インシデント対応者が有用なソフトウェアとマルウェアの違いをどのように理解できるのか疑問に思ったことはありませんか? 答えは質問そのものにあります。なぜなら、ハッカーによって急速に生成されるマルウェアについて研究しなければ、研究者や専門家が有用なソフトウェアとマルウェアの違いを知ることはまったく不可能だからです。 このセクションでは、このタスクを実行するツールである VirusShare について説明します。

VirusShareを理解する

VirusShareは、セキュリティ研究者、インシデントレスポンダー、フォレンジックアナリストにライブの悪意のあるコードのサンプルを提供するマルウェアサンプルの最大規模の非公開コレクションです。 3,000万を超えるサンプルが含まれています。

VirusShareの利点は、自由に利用できるマルウェアハッシュのリストです。 誰でもこれらのハッシュを使用して非常に包括的なハッシュセットを作成し、それを使用して潜在的に悪意のあるファイルを識別することができます。 ただし、VirusShareを使用する前に、https://virusshare.comにアクセスして詳細を確認することをお勧めします。

Pythonを使用してVirusShareから改行区切りハッシュリストを作成する

VirusShareのハッシュリストは、X-waysやEnCaseなどのさまざまなフォレンジックツールで使用できます。 以下で説明するスクリプトでは、VirusShareからのハッシュリストのダウンロードを自動化して、改行区切りのハッシュリストを作成します。

このスクリプトでは、次のようにダウンロードできるサードパーティのPythonライブラリ tqdm が必要です-

pip install tqdm

このスクリプトでは、最初にVirusShareハッシュページを読み取り、最新のハッシュリストを動的に識別することに注意してください。 次に、進行状況バーを初期化し、目的の範囲のハッシュリストをダウンロードします。

まず、次のライブラリをインポートします-

from __future__ import print_function

import argparse
import os
import ssl
import sys
import tqdm

from urllib.request import urlopen
import urllib.error

このスクリプトは、ハッシュセットの目的のパスになる位置引数を1つ取ります-

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Hash set from VirusShare')
   parser.add_argument("OUTPUT_HASH", help = "Output Hashset")
   parser.add_argument("--start", type = int, help = "Optional starting location")
   args = parser.parse_args()

さて、次のように標準入力検証を実行します-

directory = os.path.dirname(args.OUTPUT_HASH)
if not os.path.exists(directory):
   os.makedirs(directory)
if args.start:
   main(args.OUTPUT_HASH, start=args.start)
else:
   main(args.OUTPUT_HASH)

ここで、引数として** kwargs を使用して* main()*関数を定義する必要があります。これにより、以下に示すように、指定されたキー引数をサポートするために参照できる辞書が作成されます。

def main(hashset, **kwargs):
   url = "https://virusshare.com/hashes.4n6"
   print("[+] Identifying hash set range from {}".format(url))
   context = ssl._create_unverified_context()

ここで、* urlib.request.urlopen()*メソッドを使用してVirusShareハッシュページを開く必要があります。 私たちは次のようにtry-exceptブロックを使用します-

try:
   index = urlopen(url, context = context).read().decode("utf-8")
except urllib.error.HTTPError as e:
   print("[-] Error accessing webpage - exiting..")
   sys.exit(1)

次に、ダウンロードしたページから最新のハッシュリストを特定します。 これを行うには、VirusShareハッシュリストへのHTML href タグの最後のインスタンスを検索します。 それは次のコード行で行うことができます-

tag = index.rfind(r'a href = "hashes/VirusShare_')
stop = int(index[tag + 27: tag + 27 + 5].lstrip("0"))

if "start" not in kwa<rgs:
   start = 0
else:
   start = kwargs["start"]

if start < 0 or start > stop:
   print("[-] Supplied start argument must be greater than or equal ""to zero but less than the latest hash list, ""currently: {}".format(stop))
sys.exit(2)
print("[+] Creating a hashset from hash lists {} to {}".format(start, stop))
hashes_downloaded = 0

今、私たちは次のようにループとプログレスバーを作成するために* tqdm.trange()*メソッドを使用します-

for x in tqdm.trange(start, stop + 1, unit_scale=True,desc="Progress"):
   url_hash = "https://virusshare.com/hashes/VirusShare_"\"{}.md5".format(str(x).zfill(5))
   try:
      hashes = urlopen(url_hash, context=context).read().decode("utf-8")
      hashes_list = hashes.split("\n")
   except urllib.error.HTTPError as e:
      print("[-] Error accessing webpage for hash list {}"" - continuing..".format(x))
   continue

上記の手順を正常に実行した後、ハッシュセットテキストファイルをa +モードで開き、テキストファイルの最後に追加します。

with open(hashset, "a+") as hashfile:
   for line in hashes_list:
   if not line.startswith("#") and line != "":
      hashes_downloaded += 1
      hashfile.write(line + '\n')
   print("[+] Finished downloading {} hashes into {}".format(
      hashes_downloaded, hashset))

上記のスクリプトを実行すると、テキスト形式のMD5ハッシュ値を含む最新のハッシュリストが取得されます。

メールを使用した調査

前の章では、ネットワークフォレンジックの重要性とプロセス、および関連する概念について説明しました。 この章では、デジタルフォレンジックにおける電子メールの役割と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レポートとディレクトリが添付ファイルでいっぱいになります。

Windows-Iの重要なアーティファクト

この章では、Microsoft Windowsのフォレンジックに関連するさまざまな概念と、調査プロセスで調査者が取得できる重要なアーティファクトについて説明します。

前書き

アーティファクトとは、コンピューターユーザーが実行したアクティビティに関連する重要な情報を持つコンピューターシステム内のオブジェクトまたは領域です。 この情報の種類と場所は、オペレーティングシステムによって異なります。 フォレンジック分析中、これらのアーティファクトは、調査員の観察の承認または不承認において非常に重要な役割を果たします。

フォレンジックのためのWindowsアーティファクトの重要性

Windowsのアーティファクトは、次の理由により重要であると想定しています-

  • 世界のトラフィックの約90%は、Windowsをオペレーティングシステムとして使用しているコンピューターからのものです。 そのため、デジタルフォレンジック検査官にとって、Windowsアーティファクトは非常に重要です。
  • Windowsオペレーティングシステムには、コンピューターシステムでのユーザーアクティビティに関連するさまざまな種類の証拠が保存されます。 これは、デジタルフォレンジックにとってWindowsアーティファクトの重要性を示すもう1つの理由です。
  • 多くの場合、調査員は、ユーザーが作成したデータのような古い領域と従来の領域を中心に調査を展開します。 Windowsのアーティファクトは、システムが作成したデータやアーティファクトのような非伝統的な領域に調査を導くことができます。
  • 調査担当者だけでなく、非公式の調査を行う企業や個人にも役立つ、大量のアーティファクトがWindowsによって提供されます。
  • 近年のサイバー犯罪の増加は、Windowsアーティファクトが重要であるもう1つの理由です。

WindowsアーティファクトとPythonスクリプト

このセクションでは、Windowsアーティファクトとそれらから情報を取得するPythonスクリプトについて説明します。

ごみ箱

フォレンジック調査のための重要なWindowsアーティファクトの1つです。 Windowsのごみ箱には、ユーザーによって削除されたが、まだシステムによって物理的に削除されていないファイルが含まれています。 ユーザーがファイルをシステムから完全に削除したとしても、それは調査の重要なソースとなります。 これは、削除されたファイルから、審査官が元のファイルパスやごみ箱に送信された時刻などの貴重な情報を抽出できるためです。

ごみ箱の証拠の保存は、Windowsのバージョンに依存することに注意してください。 次のPythonスクリプトでは、2つのファイルを作成するWindows 7を扱います。リサイクルファイルの実際のコンテンツを含む $ R ファイルと、元のファイル名、パス、ファイルサイズを含む $ I ファイルファイルが削除されたとき。

Pythonスクリプトの場合、サードパーティのモジュール、つまり pytsk3、pyewf および unicodecsv をインストールする必要があります。 pip を使用してそれらをインストールできます。 次の手順に従って、ごみ箱から情報を抽出できます-

  • まず、再帰メソッドを使用して $ Recycle.bin フォルダーをスキャンし、 $ I で始まるすべてのファイルを選択する必要があります。
  • 次に、ファイルの内容を読み取り、使用可能なメタデータ構造を解析します。
  • 次に、関連する$ Rファイルを検索します。
  • 最後に、レビューのために結果をCSVファイルに書き込みます。

この目的のためにPythonコードを使用する方法を見てみましょう-

まず、次のPythonライブラリをインポートする必要があります-

from __future__ import print_function
from argparse import ArgumentParser

import datetime
import os
import struct

from utility.pytskutil import TSKUtil
import unicodecsv as csv

次に、コマンドラインハンドラーの引数を指定する必要があります。 以下に示すように、ここでは3つの引数を受け入れることに注意してください。最初は証拠ファイルへのパス、2番目は証拠ファイルのタイプ、3番目はCSVレポートへの望ましい出力パスです-

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Recycle Bin evidences')
   parser.add_argument('EVIDENCE_FILE', help = "Path to evidence file")
   parser.add_argument('IMAGE_TYPE', help = "Evidence file format",
   choices = ('ewf', 'raw'))
   parser.add_argument('CSV_REPORT', help = "Path to CSV report")
   args = parser.parse_args()
   main(args.EVIDENCE_FILE, args.IMAGE_TYPE, args.CSV_REPORT)

ここで、すべての処理を処理する* main()関数を定義します。 次のように *$ I ファイルを検索します-

def main(evidence, image_type, report_file):
   tsk_util = TSKUtil(evidence, image_type)
   dollar_i_files = tsk_util.recurse_files("$I", path = '/$Recycle.bin',logic = "startswith")

   if dollar_i_files is not None:
      processed_files = process_dollar_i(tsk_util, dollar_i_files)
      write_csv(report_file,['file_path', 'file_size', 'deleted_time','dollar_i_file', 'dollar_r_file', 'is_directory'],processed_files)
   else:
      print("No $I files found")

さて、 $ I ファイルが見つかった場合は、* process_dollar_i()関数に送信する必要があります。この関数は、以下に示すように、 *$ I ファイルのリストと tsk_util オブジェクトを受け入れます。

def process_dollar_i(tsk_util, dollar_i_files):
   processed_files = []

   for dollar_i in dollar_i_files:
      file_attribs = read_dollar_i(dollar_i[2])
      if file_attribs is None:
         continue
      file_attribs['dollar_i_file'] = os.path.join('/$Recycle.bin', dollar_i[1][1:])

今、次のように$ Rファイルを検索します-

recycle_file_path = os.path.join('/$Recycle.bin',dollar_i[1].rsplit("/", 1)[0][1:])
dollar_r_files = tsk_util.recurse_files(
   "$R" + dollar_i[0][2:],path = recycle_file_path, logic = "startswith")

   if dollar_r_files is None:
      dollar_r_dir = os.path.join(recycle_file_path,"$R" + dollar_i[0][2:])
      dollar_r_dirs = tsk_util.query_directory(dollar_r_dir)

   if dollar_r_dirs is None:
      file_attribs['dollar_r_file'] = "Not Found"
      file_attribs['is_directory'] = 'Unknown'

   else:
      file_attribs['dollar_r_file'] = dollar_r_dir
      file_attribs['is_directory'] = True

   else:
      dollar_r = [os.path.join(recycle_file_path, r[1][1:])for r in dollar_r_files]
      file_attribs['dollar_r_file'] = ";".join(dollar_r)
      file_attribs['is_directory'] = False
      processed_files.append(file_attribs)
   return processed_files

次に、* read_dollar_i()メソッドを定義して、 *$ I ファイルを読み取ります。つまり、メタデータを解析します。 * read_random()メソッドを使用して、署名の最初の8バイトを読み取ります。 署名が一致しない場合、これはnoneを返します。 その後、有効なファイルである場合は、 *$ I ファイルから値を読み取って展開する必要があります。

def read_dollar_i(file_obj):
   if file_obj.read_random(0, 8) != '\x01\x00\x00\x00\x00\x00\x00\x00':
      return None
   raw_file_size = struct.unpack('<q', file_obj.read_random(8, 8))
   raw_deleted_time = struct.unpack('<q',   file_obj.read_random(16, 8))
   raw_file_path = file_obj.read_random(24, 520)

今、これらのファイルを抽出した後、以下に示すように* sizeof_fmt()*関数を使用して整数を人間が読める値に解釈する必要があります-

file_size = sizeof_fmt(raw_file_size[0])
deleted_time = parse_windows_filetime(raw_deleted_time[0])

file_path = raw_file_path.decode("utf16").strip("\x00")
return {'file_size': file_size, 'file_path': file_path,'deleted_time': deleted_time}

今、私たちは次のように* sizeof_fmt()*関数を定義する必要があります-

def sizeof_fmt(num, suffix = 'B'):
   for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
      if abs(num) < 1024.0:
         return "%3.1f%s%s" % (num, unit, suffix)
      num/= 1024.0
   return "%.1f%s%s" % (num, 'Yi', suffix)

今、次のようにフォーマットされた日付と時刻に解釈された整数の関数を定義します-

def parse_windows_filetime(date_value):
   microseconds = float(date_value)/10
   ts = datetime.datetime(1601, 1, 1) + datetime.timedelta(
      microseconds = microseconds)
   return ts.strftime('%Y-%m-%d %H:%M:%S.%f')

今、私たちは次のように処理された結果をCSVファイルに書き込むために* write_csv()*メソッドを定義します-

def write_csv(outfile, fieldnames, data):
   with open(outfile, 'wb') as open_outfile:
      csvfile = csv.DictWriter(open_outfile, fieldnames)
      csvfile.writeheader()
      csvfile.writerows(data)

上記のスクリプトを実行すると、$ Iおよび$ Rファイルからデータが取得されます。

ポストイット

Windows付箋は、ペンと紙で書くという現実の習慣を置き換えます。 これらのノートは、色、フォントなどのさまざまなオプションを使用してデスクトップに浮かびました。 Windows 7では、付箋ファイルはOLEファイルとして保存されるため、次のPythonスクリプトでは、このOLEファイルを調査して付箋からメタデータを抽出します。

このPythonスクリプトでは、サードパーティのモジュール、つまり olefile、pytsk3、pyewf 、unicodecsvをインストールする必要があります。 コマンド pip を使用してそれらをインストールできます。

付箋ファイルから情報を抽出するために、以下で説明する手順、つまり StickyNote.sn を実行できます-

  • まず、証拠ファイルを開き、すべてのStickyNote.sntファイルを見つけます。
  • 次に、OLEストリームからメタデータとコンテンツを解析し、RTFコンテンツをファイルに書き込みます。
  • 最後に、このメタデータのCSVレポートを作成します。

Pythonコード

この目的のためにPythonコードを使用する方法を見てみましょう-

まず、次のPythonライブラリをインポートします-

from __future__ import print_function
from argparse import ArgumentParser

import unicodecsv as csv
import os
import StringIO

from utility.pytskutil import TSKUtil
import olefile

次に、このスクリプト全体で使用されるグローバル変数を定義します-

REPORT_COLS = ['note_id', 'created', 'modified', 'note_text', 'note_file']

次に、コマンドラインハンドラーの引数を指定する必要があります。 ここでは、3つの引数を受け入れることに注意してください。1つ目は証拠ファイルへのパス、2つ目は証拠ファイルのタイプ、3つ目は次のような望ましい出力パスです-

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Evidence from Sticky Notes')
   parser.add_argument('EVIDENCE_FILE', help="Path to evidence file")
   parser.add_argument('IMAGE_TYPE', help="Evidence file format",choices=('ewf', 'raw'))
   parser.add_argument('REPORT_FOLDER', help="Path to report folder")
   args = parser.parse_args()
   main(args.EVIDENCE_FILE, args.IMAGE_TYPE, args.REPORT_FOLDER)

さて、以下に示すように、前のスクリプトに似た* main()*関数を定義します-

def main(evidence, image_type, report_folder):
   tsk_util = TSKUtil(evidence, image_type)
   note_files = tsk_util.recurse_files('StickyNotes.snt', '/Users','equals')

次に、結果のファイルを反復処理します。 次に、* parse_snt_file()関数を呼び出してファイルを処理し、次に write_note_rtf()*メソッドを使用してRTFファイルを書き込みます。

report_details = []
for note_file in note_files:
   user_dir = note_file[1].split("/")[1]
   file_like_obj = create_file_like_obj(note_file[2])
   note_data = parse_snt_file(file_like_obj)

   if note_data is None:
      continue
   write_note_rtf(note_data, os.path.join(report_folder, user_dir))
   report_details += prep_note_report(note_data, REPORT_COLS,"/Users" + note_file[1])
   write_csv(os.path.join(report_folder, 'sticky_notes.csv'), REPORT_COLS,report_details)

次に、このスクリプトで使用されるさまざまな関数を定義する必要があります。

まず、 pytsk ファイルオブジェクトを取得して、ファイルのサイズを読み取る* create_file_like_obj()関数を定義します。 次に、入力としてファイルのようなオブジェクトを受け入れ、付箋ファイルの読み取りと解釈に使用される parse_snt_file()*関数を定義します。

def parse_snt_file(snt_file):

   if not olefile.isOleFile(snt_file):
      print("This is not an OLE file")
      return None
   ole = olefile.OleFileIO(snt_file)
   note = {}

   for stream in ole.listdir():
      if stream[0].count("-") == 3:
         if stream[0] not in note:
            note[stream[0]] = {"created": ole.getctime(stream[0]),"modified": ole.getmtime(stream[0])}
         content = None
         if stream[1] == '0':
            content = ole.openstream(stream).read()
         elif stream[1] == '3':
            content = ole.openstream(stream).read().decode("utf-16")
         if content:
            note[stream[0]][stream[1]] = content
    return note

次に、次のように* write_note_rtf()*関数を定義してRTFファイルを作成します

def write_note_rtf(note_data, report_folder):
   if not os.path.exists(report_folder):
      os.makedirs(report_folder)

   for note_id, stream_data in note_data.items():
      fname = os.path.join(report_folder, note_id + ".rtf")
      with open(fname, 'w') as open_file:
         open_file.write(stream_data['0'])

次に、ネストされた辞書を、CSVスプレッドシートにより適した辞書のフラットリストに変換します。 * prep_note_report()関数を定義することにより実行されます。 最後に、 write_csv()*関数を定義します。

def prep_note_report(note_data, report_cols, note_file):
   report_details = []

   for note_id, stream_data in note_data.items():
      report_details.append({
         "note_id": note_id,
         "created": stream_data['created'],
         "modified": stream_data['modified'],
         "note_text": stream_data['3'].strip("\x00"),
         "note_file": note_file
      })
   return report_details
def write_csv(outfile, fieldnames, data):
   with open(outfile, 'wb') as open_outfile:
      csvfile = csv.DictWriter(open_outfile, fieldnames)
      csvfile.writeheader()
      csvfile.writerows(data)

上記のスクリプトを実行した後、Sticky Notesファイルからメタデータを取得します。

レジストリファイル

Windowsレジストリファイルには、法医学アナリストにとって貴重な情報の宝庫のような多くの重要な詳細が含まれています。 これは、オペレーティングシステムの構成、ユーザーアクティビティ、ソフトウェアのインストールなどに関する詳細を含む階層型データベースです。 次のPythonスクリプトでは、 SYSTEM および SOFTWARE ハイブから共通のベースライン情報にアクセスします。

このPythonスクリプトでは、サードパーティのモジュール、つまり pytsk3、pyewf および registry をインストールする必要があります。 pip を使用してそれらをインストールできます。

私たちは、Windowsレジストリから情報を抽出するための以下の手順に従うことができます-

  • 最初に、名前とパスで処理するレジストリハイブを見つけます。
  • 次に、StringIOおよびレジストリモジュールを使用してこれらのファイルを開きます。
  • 最後に、すべてのハイブを処理し、解析した値を解釈のためにコンソールに出力する必要があります。

Pythonコード

この目的のためにPythonコードを使用する方法を見てみましょう-

まず、次のPythonライブラリをインポートします-

from __future__ import print_function
from argparse import ArgumentParser

import datetime
import StringIO
import struct

from utility.pytskutil import TSKUtil
from Registry import Registry

次に、コマンドラインハンドラーの引数を指定します。 ここでは2つの引数を受け入れます-最初に証拠ファイルへのパス、2番目に証拠ファイルのタイプ、以下に示すように-

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Evidence from Windows Registry')
   parser.add_argument('EVIDENCE_FILE', help = "Path to evidence file")
   parser.add_argument('IMAGE_TYPE', help = "Evidence file format",
   choices = ('ewf', 'raw'))
   args = parser.parse_args()
   main(args.EVIDENCE_FILE, args.IMAGE_TYPE)

次のように /Windows/System32/config フォルダー内の SYSTEM および SOFTWARE ハイブを検索するための* main()*関数を定義します-

def main(evidence, image_type):
   tsk_util = TSKUtil(evidence, image_type)
   tsk_system_hive = tsk_util.recurse_files('system', '/Windows/system32/config', 'equals')
   tsk_software_hive = tsk_util.recurse_files('software', '/Windows/system32/config', 'equals')
   system_hive = open_file_as_reg(tsk_system_hive[0][2])
   software_hive = open_file_as_reg(tsk_software_hive[0][2])
   process_system_hive(system_hive)
   process_software_hive(software_hive)

次に、レジストリファイルを開くための関数を定義します。 この目的のために、次のように pytsk メタデータからファイルのサイズを収集する必要があります-

def open_file_as_reg(reg_file):
   file_size = reg_file.info.meta.size
   file_content = reg_file.read_random(0, file_size)
   file_like_obj = StringIO.StringIO(file_content)
   return Registry.Registry(file_like_obj)

今、次の方法の助けを借りて、私たちは SYSTEM> ハイブを処理できます-

def process_system_hive(hive):
   root = hive.root()
   current_control_set = root.find_key("Select").value("Current").value()
   control_set = root.find_key("ControlSet{:03d}".format(current_control_set))
   raw_shutdown_time = struct.unpack(
      '<Q', control_set.find_key("Control").find_key("Windows").value("ShutdownTime").value())

   shutdown_time = parse_windows_filetime(raw_shutdown_time[0])
   print("Last Shutdown Time: {}".format(shutdown_time))

   time_zone = control_set.find_key("Control").find_key("TimeZoneInformation")
      .value("TimeZoneKeyName").value()

   print("Machine Time Zone: {}".format(time_zone))
   computer_name = control_set.find_key("Control").find_key("ComputerName").find_key("ComputerName")
      .value("ComputerName").value()

   print("Machine Name: {}".format(computer_name))
   last_access = control_set.find_key("Control").find_key("FileSystem")
      .value("NtfsDisableLastAccessUpdate").value()
   last_access = "Disabled" if last_access == 1 else "enabled"
   print("Last Access Updates: {}".format(last_access))

今、私たちは次のようにフォーマットされた日付と時刻に整数を解釈するための関数を定義する必要があります-

def parse_windows_filetime(date_value):
   microseconds = float(date_value)/10
   ts = datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds = microseconds)
   return ts.strftime('%Y-%m-%d %H:%M:%S.%f')

def parse_unix_epoch(date_value):
   ts = datetime.datetime.fromtimestamp(date_value)
   return ts.strftime('%Y-%m-%d %H:%M:%S.%f')

今、次の方法の助けを借りて、*ソフトウェア*ハイブを処理できます-

def process_software_hive(hive):
   root = hive.root()
   nt_curr_ver = root.find_key("Microsoft").find_key("Windows NT")
      .find_key("CurrentVersion")

   print("Product name: {}".format(nt_curr_ver.value("ProductName").value()))
   print("CSD Version: {}".format(nt_curr_ver.value("CSDVersion").value()))
   print("Current Build: {}".format(nt_curr_ver.value("CurrentBuild").value()))
   print("Registered Owner: {}".format(nt_curr_ver.value("RegisteredOwner").value()))
   print("Registered Org:
      {}".format(nt_curr_ver.value("RegisteredOrganization").value()))

   raw_install_date = nt_curr_ver.value("InstallDate").value()
   install_date = parse_unix_epoch(raw_install_date)
   print("Installation Date: {}".format(install_date))

上記のスクリプトを実行した後、メタデータをWindowsレジストリファイルに保存します。

Windows-IIの重要なアーティファクト

この章では、Windowsの重要なアーティファクトとPythonを使用した抽出方法について説明します。

ユーザー活動

さまざまなユーザーアクティビティを保存するための NTUSER.DAT ファイルを持つWindows。 すべてのユーザープロファイルには、 NTUSER.DAT のようなハイブがあります。このハイブには、そのユーザーに特に関連する情報と構成が保存されます。 したがって、法医学アナリストによる調査の目的には非常に有用です。

次のPythonスクリプトは、システム上のユーザーのアクションを調べるために NTUSER.DAT のキーの一部を解析します。 先に進む前に、Pythonスクリプトの場合、サードパーティのモジュール、つまり Registry、pytsk3 、pyewfおよび Jinja2 をインストールする必要があります。 pipを使用してインストールできます。

次の手順に従って、 NTUSER.DAT ファイルから情報を抽出できます-

  • まず、システム内のすべての NTUSER.DAT ファイルを検索します。
  • 次に、各 NTUSER.DAT ファイルの WordWheelQuery、TypePathおよびRunMRU キーを解析します。
  • 最後に、 Jinja2 fmoduleを使用して、既に処理されたこれらのアーティファクトをHTMLレポートに書き込みます。

Pythonコード

この目的のためにPythonコードを使用する方法を見てみましょう-

まず、次のPythonモジュールをインポートする必要があります-

from __future__ import print_function
from argparse import ArgumentParser

import os
import StringIO
import struct

from utility.pytskutil import TSKUtil
from Registry import Registry
import jinja2

次に、コマンドラインハンドラーの引数を指定します。 ここでは、3つの引数を受け入れます-最初は証拠ファイルへのパス、2番目は証拠ファイルのタイプ、3番目はHTMLレポートへの望ましい出力パスです、以下に示すように-

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Information from user activities')
   parser.add_argument('EVIDENCE_FILE',help = "Path to evidence file")
   parser.add_argument('IMAGE_TYPE',help = "Evidence file format",choices = ('ewf', 'raw'))
   parser.add_argument('REPORT',help = "Path to report file")
   args = parser.parse_args()
   main(args.EVIDENCE_FILE, args.IMAGE_TYPE, args.REPORT)

次に、示されているように、すべての NTUSER.DAT ファイルを検索するための* main()*関数を定義しましょう-

def main(evidence, image_type, report):
   tsk_util = TSKUtil(evidence, image_type)
   tsk_ntuser_hives = tsk_util.recurse_files('ntuser.dat','/Users', 'equals')

   nt_rec = {
      'wordwheel': {'data': [], 'title': 'WordWheel Query'},
      'typed_path': {'data': [], 'title': 'Typed Paths'},
      'run_mru': {'data': [], 'title': 'Run MRU'}
   }

今、私たちは NTUSER.DAT ファイルでキーを見つけようとし、それを見つけたら、以下に示すようにユーザー処理機能を定義します-

for ntuser in tsk_ntuser_hives:
   uname = ntuser[1].split("/")

open_ntuser = open_file_as_reg(ntuser[2])
try:
   explorer_key = open_ntuser.root().find_key("Software").find_key("Microsoft")
      .find_key("Windows").find_key("CurrentVersion").find_key("Explorer")
   except Registry.RegistryKeyNotFoundException:
      continue
   nt_rec['wordwheel']['data'] += parse_wordwheel(explorer_key, uname)
   nt_rec['typed_path']['data'] += parse_typed_paths(explorer_key, uname)
   nt_rec['run_mru']['data'] += parse_run_mru(explorer_key, uname)
   nt_rec['wordwheel']['headers'] = \ nt_rec['wordwheel']['data'][0].keys()
   nt_rec['typed_path']['headers'] = \ nt_rec['typed_path']['data'][0].keys()
   nt_rec['run_mru']['headers'] = \ nt_rec['run_mru']['data'][0].keys()

今、次のように* write_html()*メソッドに辞書オブジェクトとそのパスを渡します-

write_html(report, nt_rec)

次に、 pytsk ファイルハンドルを取得し、 StringIO クラスを介してRegistryクラスに読み込むメソッドを定義します。

def open_file_as_reg(reg_file):
   file_size = reg_file.info.meta.size
   file_content = reg_file.read_random(0, file_size)
   file_like_obj = StringIO.StringIO(file_content)
   return Registry.Registry(file_like_obj)

今、私たちは次のように NTUSER.DAT ファイルから WordWheelQuery キーを解析して処理する関数を定義します-

def parse_wordwheel(explorer_key, username):
   try:
      wwq = explorer_key.find_key("WordWheelQuery")
   except Registry.RegistryKeyNotFoundException:
      return []
   mru_list = wwq.value("MRUListEx").value()
   mru_order = []

   for i in xrange(0, len(mru_list), 2):
      order_val = struct.unpack('h', mru_list[i:i + 2])[0]
   if order_val in mru_order and order_val in (0, -1):
      break
   else:
      mru_order.append(order_val)
   search_list = []

   for count, val in enumerate(mru_order):
      ts = "N/A"
      if count == 0:
         ts = wwq.timestamp()
      search_list.append({
         'timestamp': ts,
         'username': username,
         'order': count,
         'value_name': str(val),
         'search': wwq.value(str(val)).value().decode("UTF-16").strip("\x00")
})
   return search_list

ここで、次のように NTUSER.DAT ファイルから TypedPaths キーを解析および処理する関数を定義します-

def parse_typed_paths(explorer_key, username):
   try:
      typed_paths = explorer_key.find_key("TypedPaths")
   except Registry.RegistryKeyNotFoundException:
      return []
   typed_path_details = []

   for val in typed_paths.values():
      typed_path_details.append({
         "username": username,
         "value_name": val.name(),
         "path": val.value()
      })
   return typed_path_details

今、私たちは次のように NTUSER.DAT ファイルから RunMRU キーを解析して処理する関数を定義します-

def parse_run_mru(explorer_key, username):
   try:
      run_mru = explorer_key.find_key("RunMRU")
   except Registry.RegistryKeyNotFoundException:
      return []

   if len(run_mru.values()) == 0:
      return []
   mru_list = run_mru.value("MRUList").value()
   mru_order = []

   for i in mru_list:
      mru_order.append(i)
   mru_details = []

   for count, val in enumerate(mru_order):
      ts = "N/A"
      if count == 0:
         ts = run_mru.timestamp()
      mru_details.append({
         "username": username,
         "timestamp": ts,
         "order": count,
         "value_name": val,
         "run_statement": run_mru.value(val).value()
      })
   return mru_details

さて、次の機能はHTMLレポートの作成を処理します-

def write_html(outfile, data_dict):
   cwd = os.path.dirname(os.path.abspath(__file__))
   env = jinja2.Environment(loader=jinja2.FileSystemLoader(cwd))
   template = env.get_template("user_activityl")
   rendering = template.render(nt_data=data_dict)

   with open(outfile, 'w') as open_outfile:
      open_outfile.write(rendering)

最後に、レポート用のHTMLドキュメントを作成できます。 上記のスクリプトを実行した後、NTUSER.DATファイルからHTMLドキュメント形式の情報を取得します。

LINKファイル

ショートカットファイルは、ユーザーまたはオペレーティングシステムが、頻繁に使用、ダブルクリック、または接続ストレージなどのシステムドライブからアクセスされるファイルのショートカットファイルを作成するときに作成されます。 このような種類のショートカットファイルは、リンクファイルと呼ばれます。 これらのリンクファイルにアクセスすることにより、調査員はこれらのファイルにアクセスした時間や場所などのウィンドウのアクティビティを見つけることができます。

これらのWindows LINKファイルから情報を取得するために使用できるPythonスクリプトについて説明しましょう。

Pythonスクリプトの場合、サードパーティのモジュール、つまり pylnk、pytsk3、pyewf をインストールします。 次の手順に従って、 lnk ファイルから情報を抽出できます。

  • まず、システム内で lnk ファイルを検索します。
  • 次に、ファイルを反復処理して、そのファイルから情報を抽出します。
  • さて、最後にこの情報をCSVレポートに追加する必要があります。

Pythonコード

この目的のためにPythonコードを使用する方法を見てみましょう-

まず、次のPythonライブラリをインポートします-

from __future__ import print_function
from argparse import ArgumentParser

import csv
import StringIO

from utility.pytskutil import TSKUtil
import pylnk

次に、コマンドラインハンドラーの引数を指定します。 ここでは、3つの引数を受け入れます。1つ目は証拠ファイルへのパス、2つ目は証拠ファイルのタイプ、3つ目はCSVレポートへの望ましい出力パスです(以下を参照)-

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Parsing LNK files')
   parser.add_argument('EVIDENCE_FILE', help = "Path to evidence file")
   parser.add_argument('IMAGE_TYPE', help = "Evidence file format",choices = ('ewf', 'raw'))
   parser.add_argument('CSV_REPORT', help = "Path to CSV report")
   args = parser.parse_args()
   main(args.EVIDENCE_FILE, args.IMAGE_TYPE, args.CSV_REPORT)

次に、 TSKUtil のオブジェクトを作成して証拠ファイルを解釈し、ファイルシステムを反復処理して、 lnk で終わるファイルを見つけます。 それは次のように* main()*関数を定義することで実行できます-

def main(evidence, image_type, report):
   tsk_util = TSKUtil(evidence, image_type)
   lnk_files = tsk_util.recurse_files("lnk", path="/", logic="endswith")

   if lnk_files is None:
      print("No lnk files found")
      exit(0)
   columns = [
      'command_line_arguments', 'description', 'drive_serial_number',
      'drive_type', 'file_access_time', 'file_attribute_flags',
      'file_creation_time', 'file_modification_time', 'file_size',
      'environmental_variables_location', 'volume_label',
      'machine_identifier', 'local_path', 'network_path',
      'relative_path', 'working_directory'
   ]

今、次のコードの助けを借りて、次のような関数を作成して lnk ファイルを反復処理します-

parsed_lnks = []

for entry in lnk_files:
   lnk = open_file_as_lnk(entry[2])
   lnk_data = {'lnk_path': entry[1], 'lnk_name': entry[0]}

   for col in columns:
      lnk_data[col] = getattr(lnk, col, "N/A")
   lnk.close()
   parsed_lnks.append(lnk_data)
write_csv(report, columns + ['lnk_path', 'lnk_name'], parsed_lnks)

次に、2つの関数を定義する必要があります。1つは pytsk ファイルオブジェクトを開き、もう1つは以下に示すようにCSVレポートの作成に使用されます-

def open_file_as_lnk(lnk_file):
   file_size = lnk_file.info.meta.size
   file_content = lnk_file.read_random(0, file_size)
   file_like_obj = StringIO.StringIO(file_content)
   lnk = pylnk.file()
   lnk.open_file_object(file_like_obj)
   return lnk
def write_csv(outfile, fieldnames, data):
   with open(outfile, 'wb') as open_outfile:
      csvfile = csv.DictWriter(open_outfile, fieldnames)
      csvfile.writeheader()
      csvfile.writerows(data)

上記のスクリプトを実行した後、CSVレポートで検出された lnk ファイルから情報を取得します-

ファイルのプリフェッチ

アプリケーションが特定の場所から初めて実行されるたびに、Windowsは prefetch files を作成します。 これらは、アプリケーションの起動プロセスを高速化するために使用されます。 これらのファイルの拡張子は .PF で、*” \ Root \ Windows \ Prefetch” *フォルダーに保存されます。

デジタルフォレンジックの専門家は、ユーザーの詳細とともに、指定された場所からのプログラム実行の証拠を明らかにすることができます。 プリフェッチファイルは、プログラムが削除またはアンインストールされた後でもエントリが残るため、審査官にとって有用なアーティファクトです。

以下に示すように、Windowsプリフェッチファイルから情報を取得するPythonスクリプトについて説明しましょう-

Pythonスクリプトの場合、サードパーティのモジュール、つまり pylnk、pytsk3 および unicodecsv をインストールします。 前の章で説明したPythonスクリプトでこれらのライブラリを使用したことを思い出してください。

*prefetch* ファイルから情報を抽出するには、以下の手順に従う必要があります-
  • まず、*。pf *拡張ファイルまたはプリフェッチファイルをスキャンします。
  • 次に、署名検証を実行して、誤検知を排除します。
  • 次に、Windowsプリフェッチファイル形式を解析します。 これは、Windowsバージョンによって異なります。 たとえば、Windows XPでは17、Windows VistaおよびWindows 7では23、Windows 8.1では26、Windows 10では30です。
  • 最後に、解析結果をCSVファイルに書き込みます。

Pythonコード

この目的のためにPythonコードを使用する方法を見てみましょう-

まず、次のPythonライブラリをインポートします-

from __future__ import print_function
import argparse
from datetime import datetime, timedelta

import os
import pytsk3
import pyewf
import struct
import sys
import unicodecsv as csv
from utility.pytskutil import TSKUtil

次に、コマンドラインハンドラーの引数を指定します。 ここでは、2つの引数を受け入れます。1つ目は証拠ファイルへのパス、2つ目は証拠ファイルのタイプです。 また、プリフェッチファイルをスキャンするパスを指定するためのオプションの引数も受け入れます-

if __name__ == "__main__":
   parser = argparse.ArgumentParser('Parsing Prefetch files')
   parser.add_argument("EVIDENCE_FILE", help = "Evidence file path")
   parser.add_argument("TYPE", help = "Type of Evidence",choices = ("raw", "ewf"))
   parser.add_argument("OUTPUT_CSV", help = "Path to write output csv")
   parser.add_argument("-d", help = "Prefetch directory to scan",default = "/WINDOWS/PREFETCH")
   args = parser.parse_args()

   if os.path.exists(args.EVIDENCE_FILE) and \
      os.path.isfile(args.EVIDENCE_FILE):
   main(args.EVIDENCE_FILE, args.TYPE, args.OUTPUT_CSV, args.d)
else:
   print("[-] Supplied input file {} does not exist or is not a ""file".format(args.EVIDENCE_FILE))
   sys.exit(1)

次に、 TSKUtil のオブジェクトを作成して証拠ファイルを解釈し、ファイルシステムを反復処理して、。pf *で終わるファイルを見つけます。 それは次のように main()*関数を定義することで実行できます-

def main(evidence, image_type, output_csv, path):
   tsk_util = TSKUtil(evidence, image_type)
   prefetch_dir = tsk_util.query_directory(path)
   prefetch_files = None

   if prefetch_dir is not None:
      prefetch_files = tsk_util.recurse_files(".pf", path=path, logic="endswith")

   if prefetch_files is None:
      print("[-] No .pf files found")
      sys.exit(2)
   print("[+] Identified {} potential prefetch files".format(len(prefetch_files)))
   prefetch_data = []

   for hit in prefetch_files:
      prefetch_file = hit[2]
      pf_version = check_signature(prefetch_file)

次に、以下に示すように署名の検証を行うメソッドを定義します-

def check_signature(prefetch_file):
   version, signature = struct.unpack("^<2i", prefetch_file.read_random(0, 8))

   if signature == 1094927187:
      return version
   else:
      return None

   if pf_version is None:
      continue
   pf_name = hit[0]

   if pf_version == 17:
      parsed_data = parse_pf_17(prefetch_file, pf_name)
      parsed_data.append(os.path.join(path, hit[1].lstrip("//")))
      prefetch_data.append(parsed_data)

次に、Windowsプリフェッチファイルの処理を開始します。 ここでは、Windows XPプリフェッチファイルの例を取り上げています-

def parse_pf_17(prefetch_file, pf_name):
   create = convert_unix(prefetch_file.info.meta.crtime)
   modify = convert_unix(prefetch_file.info.meta.mtime)
def convert_unix(ts):
   if int(ts) == 0:
      return ""
   return datetime.utcfromtimestamp(ts)
def convert_filetime(ts):
   if int(ts) == 0:
      return ""
   return datetime(1601, 1, 1) + timedelta(microseconds=ts/10)

今、次のように構造体を使用して、プリフェッチされたファイル内に埋め込まれたデータを抽出します-

pf_size, name, vol_info, vol_entries, vol_size, filetime, \
   count = struct.unpack("<i60s32x3iq16xi",prefetch_file.read_random(12, 136))
name = name.decode("utf-16", "ignore").strip("/x00").split("/x00")[0]

vol_name_offset, vol_name_length, vol_create, \
   vol_serial = struct.unpack("<2iqi",prefetch_file.read_random(vol_info, 20))
   vol_serial = hex(vol_serial).lstrip("0x")
   vol_serial = vol_serial[:4] + "-" + vol_serial[4:]
   vol_name = struct.unpack(
      "<{}s".format(2 *vol_name_length),
      prefetch_file.read_random(vol_info + vol_name_offset,vol_name_length* 2))[0]

vol_name = vol_name.decode("utf-16", "ignore").strip("/x00").split("/x00")[0]
return [
   pf_name, name, pf_size, create,
   modify, convert_filetime(filetime), count, vol_name,
   convert_filetime(vol_create), vol_serial ]

Windows XPのプリフェッチバージョンを提供しましたが、他のWindowsのプリフェッチバージョンに遭遇した場合はどうでしょうか。 その後、次のようにエラーメッセージを表示する必要があります-

elif pf_version == 23:
   print("[-] Windows Vista/7 PF file {} -- unsupported".format(pf_name))
   continue
elif pf_version == 26:
   print("[-] Windows 8 PF file {} -- unsupported".format(pf_name))
   continue
elif pf_version == 30:
   print("[-] Windows 10 PF file {} -- unsupported".format(pf_name))
continue

else:
   print("[-] Signature mismatch - Name: {}\nPath: {}".format(hit[0], hit[1]))
continue
write_output(prefetch_data, output_csv)

今、次のようにCSVレポートに結果を書き込むための方法を定義します-

def write_output(data, output_csv):
   print("[+] Writing csv report")
   with open(output_csv, "wb") as outfile:
      writer = csv.writer(outfile)
      writer.writerow([
         "File Name", "Prefetch Name", "File Size (bytes)",
         "File Create Date (UTC)", "File Modify Date (UTC)",
         "Prefetch Last Execution Date (UTC)",
         "Prefetch Execution Count", "Volume", "Volume Create Date",
         "Volume Serial", "File Path" ])
      writer.writerows(data)

上記のスクリプトを実行した後、Windows XPバージョンのプリフェッチファイルからスプレッドシートに情報を取得します。

Windows-IIIの重要なアーティファクト

この章では、Windowsでのフォレンジック分析中に調査者が取得できるさらなる成果物について説明します。

イベントログ

Windowsイベントログファイルは、名前のとおり、ユーザーがコンピューターにログオンしたとき、プログラムでエラーが発生したとき、システムの変更、RDPアクセス、アプリケーション固有のイベントなどの重要なイベントを格納する特別なファイルです。 サイバー調査員は、システムのアクセスに関する多くの有用な履歴情報を提供するため、イベントログ情報に常に関心を持っています。 次のPythonスクリプトでは、従来のWindowsイベントログ形式と現在のWindowsイベントログ形式の両方を処理します。

Pythonスクリプトの場合、サードパーティのモジュール、つまり* pytsk3、pyewf、unicodecsv、pyevt、およびpyevt * xをインストールする必要があります。 イベントログから情報を抽出するには、以下に示す手順に従ってください-

  • まず、入力引数に一致するすべてのイベントログを検索します。
  • 次に、ファイル署名の検証を実行します。
  • 次に、見つかった各イベントログを適切なライブラリで処理します。
  • 最後に、出力をスプレッドシートに書き込みます。

Pythonコード

この目的のためにPythonコードを使用する方法を見てみましょう-

まず、次のPythonライブラリをインポートします-

from __future__ import print_function
import argparse
import unicodecsv as csv
import os
import pytsk3
import pyewf
import pyevt
import pyevtx
import sys
from utility.pytskutil import TSKUtil

次に、コマンドラインハンドラーの引数を指定します。 ここでは、3つの引数を受け入れることに注意してください。1つ目は証拠ファイルへのパス、2つ目は証拠ファイルのタイプ、3つ目は処理するイベントログの名前です。

if __name__ == "__main__":
   parser = argparse.ArgumentParser('Information from Event Logs')
   parser.add_argument("EVIDENCE_FILE", help = "Evidence file path")
   parser.add_argument("TYPE", help = "Type of Evidence",choices = ("raw", "ewf"))
   parser.add_argument(
      "LOG_NAME",help = "Event Log Name (SecEvent.Evt, SysEvent.Evt, ""etc.)")

   parser.add_argument(
      "-d", help = "Event log directory to scan",default = "/WINDOWS/SYSTEM32/WINEVT")

   parser.add_argument(
      "-f", help = "Enable fuzzy search for either evt or"" evtx extension", action = "store_true")
   args = parser.parse_args()

   if os.path.exists(args.EVIDENCE_FILE) and \ os.path.isfile(args.EVIDENCE_FILE):
      main(args.EVIDENCE_FILE, args.TYPE, args.LOG_NAME, args.d, args.f)
   else:
      print("[-] Supplied input file {} does not exist or is not a ""file".format(args.EVIDENCE_FILE))
   sys.exit(1)

次に、イベントログと対話して、 TSKUtil オブジェクトを作成することにより、ユーザーが指定したパスの存在を照会します。 それは次のように* main()*メソッドの助けを借りて行うことができます-

def main(evidence, image_type, log, win_event, fuzzy):
   tsk_util = TSKUtil(evidence, image_type)
   event_dir = tsk_util.query_directory(win_event)

   if event_dir is not None:
      if fuzzy is True:
         event_log = tsk_util.recurse_files(log, path=win_event)
   else:
      event_log = tsk_util.recurse_files(log, path=win_event, logic="equal")

   if event_log is not None:
      event_data = []
      for hit in event_log:
         event_file = hit[2]
         temp_evt = write_file(event_file)

今、私たちは現在のディレクトリにコンテンツ全体を書き込むメソッドを定義することによって署名検証を実行する必要があります-

def write_file(event_file):
   with open(event_file.info.name.name, "w") as outfile:
      outfile.write(event_file.read_random(0, event_file.info.meta.size))
   return event_file.info.name.name
      if pyevt.check_file_signature(temp_evt):
         evt_log = pyevt.open(temp_evt)
         print("[+] Identified {} records in {}".format(
            evt_log.number_of_records, temp_evt))

         for i, record in enumerate(evt_log.records):
            strings = ""
            for s in record.strings:
               if s is not None:
                  strings += s + "\n"
            event_data.append([
               i, hit[0], record.computer_name,
               record.user_security_identifier,
               record.creation_time, record.written_time,
               record.event_category, record.source_name,
               record.event_identifier, record.event_type,
               strings, "",
               os.path.join(win_event, hit[1].lstrip("//"))
            ])
      elif pyevtx.check_file_signature(temp_evt):
         evtx_log = pyevtx.open(temp_evt)
         print("[+] Identified {} records in {}".format(
            evtx_log.number_of_records, temp_evt))
         for i, record in enumerate(evtx_log.records):
            strings = ""
            for s in record.strings:
               if s is not None:
               strings += s + "\n"
         event_data.append([
            i, hit[0], record.computer_name,
            record.user_security_identifier, "",
            record.written_time, record.event_level,
            record.source_name, record.event_identifier,
            "", strings, record.xml_string,
            os.path.join(win_event, hit[1].lstrip("//"))
      ])
      else:
         print("[-] {} not a valid event log. Removing temp" file...".format(temp_evt))
         os.remove(temp_evt)
      continue
      write_output(event_data)
   else:
      print("[-] {} Event log not found in {} directory".format(log, win_event))
      sys.exit(3)
else:
   print("[-] Win XP Event Log Directory {} not found".format(win_event))
   sys.exit(2

最後に、次のようにスプレッドシートに出力を書き込む方法を定義します-

def write_output(data):
   output_name = "parsed_event_logs.csv"
   print("[+] Writing {} to current working directory: {}".format(
      output_name, os.getcwd()))

   with open(output_name, "wb") as outfile:
      writer = csv.writer(outfile)
      writer.writerow([
         "Index", "File name", "Computer Name", "SID",
         "Event Create Date", "Event Written Date",
         "Event Category/Level", "Event Source", "Event ID",
         "Event Type", "Data", "XML Data", "File Path"
      ])
      writer.writerows(data)

上記のスクリプトを正常に実行すると、スプレッドシートでイベントログの情報を取得します。

インターネットの歴史

インターネット履歴は、法医学アナリストにとって非常に役立ちます。ほとんどのサイバー犯罪はインターネット上でのみ発生するためです。 Windowsのフォレンジックについて説明しているように、Internet Explorerからインターネット履歴を抽出する方法を見てみましょう。InternetExplorerはデフォルトでWindowsに付属しています。

Internet Explorerでは、インターネット履歴は index.dat ファイルに保存されます。 index.dat ファイルから情報を抽出するPythonスクリプトを見てみましょう。

以下の手順に従って、 index.dat ファイルから情報を抽出できます-

  • 最初に、システム内で index.dat ファイルを検索します。
  • 次に、ファイルを反復処理して、そのファイルから情報を抽出します。
  • 次に、これらすべての情報をCSVレポートに書き込みます。

Pythonコード

この目的のためにPythonコードを使用する方法を見てみましょう-

まず、次のPythonライブラリをインポートします-

from __future__ import print_function
import argparse

from datetime import datetime, timedelta
import os
import pytsk3
import pyewf
import pymsiecf
import sys
import unicodecsv as csv

from utility.pytskutil import TSKUtil

次に、コマンドラインハンドラーの引数を指定します。 ここでは、2つの引数を受け入れることに注意してください。1つ目は証拠ファイルへのパス、2つ目は証拠ファイルのタイプです-

if __name__ == "__main__":
parser = argparse.ArgumentParser('getting information from internet history')
   parser.add_argument("EVIDENCE_FILE", help = "Evidence file path")
   parser.add_argument("TYPE", help = "Type of Evidence",choices = ("raw", "ewf"))
   parser.add_argument("-d", help = "Index.dat directory to scan",default = "/USERS")
   args = parser.parse_args()

   if os.path.exists(args.EVIDENCE_FILE) and os.path.isfile(args.EVIDENCE_FILE):
      main(args.EVIDENCE_FILE, args.TYPE, args.d)
   else:
      print("[-] Supplied input file {} does not exist or is not a ""file".format(args.EVIDENCE_FILE))
      sys.exit(1)

次に、 TSKUtil のオブジェクトを作成して証拠ファイルを解釈し、ファイルシステムを反復処理してindex.datファイルを見つけます。 それは次のように* main()*関数を定義することで実行できます-

def main(evidence, image_type, path):
   tsk_util = TSKUtil(evidence, image_type)
   index_dir = tsk_util.query_directory(path)

   if index_dir is not None:
      index_files = tsk_util.recurse_files("index.dat", path = path,logic = "equal")

      if index_files is not None:
         print("[+] Identified {} potential index.dat files".format(len(index_files)))
         index_data = []

         for hit in index_files:
            index_file = hit[2]
            temp_index = write_file(index_file)

今、index.datファイルの情報を現在の作業ディレクトリにコピーし、後でサードパーティのモジュールで処理できるようにする機能を定義します-

def write_file(index_file):
   with open(index_file.info.name.name, "w") as outfile:
   outfile.write(index_file.read_random(0, index_file.info.meta.size))
return index_file.info.name.name

さて、組み込み関数、すなわち* check_file_signature()*の助けを借りて署名検証を実行するために次のコードを使用します-

if pymsiecf.check_file_signature(temp_index):
   index_dat = pymsiecf.open(temp_index)
   print("[+] Identified {} records in {}".format(
   index_dat.number_of_items, temp_index))

   for i, record in enumerate(index_dat.items):
   try:
      data = record.data
   if data is not None:
      data = data.rstrip("\x00")
   except AttributeError:

   if isinstance(record, pymsiecf.redirected):
      index_data.append([
         i, temp_index, "", "", "", "", "",record.location, "", "", record.offset,os.path.join(path, hit[1].lstrip("//"))])

   elif isinstance(record, pymsiecf.leak):
      index_data.append([
         i, temp_index, record.filename, "","", "", "", "", "", "", record.offset,os.path.join(path, hit[1].lstrip("//"))])
   continue

   index_data.append([
      i, temp_index, record.filename,
      record.type, record.primary_time,
      record.secondary_time,
      record.last_checked_time, record.location,
      record.number_of_hits, data, record.offset,
      os.path.join(path, hit[1].lstrip("//"))
   ])
   else:
      print("[-] {} not a valid index.dat file. Removing "
      "temp file..".format(temp_index))
      os.remove("index.dat")
      continue
      os.remove("index.dat")
      write_output(index_data)
   else:
      print("[-] Index.dat files not found in {} directory".format(path))
   sys.exit(3)
   else:
      print("[-] Directory {} not found".format(win_event))
   sys.exit(2)

次に、以下に示すように、CSVファイルで出力を印刷するメソッドを定義します-

def write_output(data):
   output_name = "Internet_Indexdat_Summary_Report.csv"
   print("[+] Writing {} with {} parsed index.dat files to current "
   "working directory: {}".format(output_name, len(data),os.getcwd()))

   with open(output_name, "wb") as outfile:
      writer = csv.writer(outfile)
      writer.writerow(["Index", "File Name", "Record Name",
      "Record Type", "Primary Date", "Secondary Date",
      "Last Checked Date", "Location", "No. of Hits",
      "Record Data", "Record Offset", "File Path"])
      writer.writerows(data)

上記のスクリプトを実行した後、CSVファイルのindex.datファイルから情報を取得します。

ボリュームシャドウコピー

シャドウコピーは、Windowsに含まれるコンピューターファイルのバックアップコピーまたはスナップショットを手動または自動で取得するためのテクノロジです。 ボリュームスナップショットサービスまたはボリュームシャドウサービス(VSS)とも呼ばれます。

これらのVSSファイルの助けを借りて、法医学の専門家は、システムが時間の経過とともにどのように変化したか、コンピューターに存在したファイルに関する履歴情報を得ることができます。 シャドウコピーテクノロジでは、シャドウコピーを作成および保存するために、ファイルシステムがNTFSである必要があります。

このセクションでは、フォレンジック画像に存在するシャドウコピーのボリュームへのアクセスに役立つPythonスクリプトを確認します。

Pythonスクリプトの場合、サードパーティのモジュール、つまり pytsk3、pyewf、unicodecsv、pyvshadow および vss をインストールする必要があります。 以下の手順に従って、VSSファイルから情報を抽出できます。

  • 最初に、RAWイメージのボリュームにアクセスし、すべてのNTFSパーティションを特定します。
  • 次に、シャドウコピーを繰り返し処理して情報を抽出します。
  • ここで、最後にスナップショット内のデータのファイルリストを作成する必要があります。

Pythonコード

この目的のためにPythonコードを使用する方法を見てみましょう-

まず、次のPythonライブラリをインポートします-

from __future__ import print_function
import argparse
from datetime import datetime, timedelta

import os
import pytsk3
import pyewf
import pyvshadow
import sys
import unicodecsv as csv

from utility import vss
from utility.pytskutil import TSKUtil
from utility import pytskutil

次に、コマンドラインハンドラーの引数を指定します。 ここでは、2つの引数を受け入れます。1つ目は証拠ファイルへのパス、2つ目は出力ファイルです。

if __name__ == "__main__":
   parser = argparse.ArgumentParser('Parsing Shadow Copies')
   parser.add_argument("EVIDENCE_FILE", help = "Evidence file path")
   parser.add_argument("OUTPUT_CSV", help = "Output CSV with VSS file listing")
   args = parser.parse_args()

ここで、入力ファイルのパスの存在を検証し、ディレクトリを出力ファイルから分離します。

directory = os.path.dirname(args.OUTPUT_CSV)
if not os.path.exists(directory) and directory != "":
   os.makedirs(directory)
if os.path.exists(args.EVIDENCE_FILE) and \ os.path.isfile(args.EVIDENCE_FILE):
   main(args.EVIDENCE_FILE, args.OUTPUT_CSV)
else:
   print("[-] Supplied input file {} does not exist or is not a "
   "file".format(args.EVIDENCE_FILE))

   sys.exit(1)

次に、 TSKUtil オブジェクトを作成して、証拠ファイルのボリュームを操作します。 それは次のように* main()*メソッドの助けを借りて行うことができます-

def main(evidence, output):
   tsk_util = TSKUtil(evidence, "raw")
   img_vol = tsk_util.return_vol()

if img_vol is not None:
   for part in img_vol:
      if tsk_util.detect_ntfs(img_vol, part):
         print("Exploring NTFS Partition for VSS")
         explore_vss(evidence, part.start * img_vol.info.block_size,output)
      else:
         print("[-] Must be a physical preservation to be compatible ""with this script")
         sys.exit(2)

今、次のように解析されたボリュームシャドウファイルを探索するためのメソッドを定義します-

def explore_vss(evidence, part_offset, output):
   vss_volume = pyvshadow.volume()
   vss_handle = vss.VShadowVolume(evidence, part_offset)
   vss_count = vss.GetVssStoreCount(evidence, part_offset)

   if vss_count > 0:
      vss_volume.open_file_object(vss_handle)
      vss_data = []

      for x in range(vss_count):
         print("Gathering data for VSC {} of {}".format(x, vss_count))
         vss_store = vss_volume.get_store(x)
         image = vss.VShadowImgInfo(vss_store)
         vss_data.append(pytskutil.openVSSFS(image, x))
write_csv(vss_data, output)

最後に、スプレッドシートに結果を書き込む方法を次のように定義します-

def write_csv(data, output):
   if data == []:
      print("[-] No output results to write")
      sys.exit(3)
   print("[+] Writing output to {}".format(output))
   if os.path.exists(output):
      append = True
with open(output, "ab") as csvfile:
      csv_writer = csv.writer(csvfile)
      headers = ["VSS", "File", "File Ext", "File Type", "Create Date",
         "Modify Date", "Change Date", "Size", "File Path"]
      if not append:
         csv_writer.writerow(headers)
      for result_list in data:
         csv_writer.writerows(result_list)

このPythonスクリプトを正常に実行すると、VSSにある情報をスプレッドシートに取得します。

ログベースのアーティファクトの調査

これまで、Pythonを使用してWindowsでアーティファクトを取得する方法を見てきました。 この章では、Pythonを使用したログベースのアーティファクトの調査について学びましょう。

前書き

ログベースのアーティファクトは、デジタルフォレンジックの専門家にとって非常に役立つ情報の宝庫です。 情報を収集するためのさまざまな監視ソフトウェアがありますが、それらから有用な情報を解析するための主な問題は、大量のデータが必要なことです。

さまざまなログベースのアーティファクトとPythonでの調査

このセクションでは、さまざまなログベースのアーティファクトとPythonでの調査について説明します-

タイムスタンプ

タイムスタンプは、ログのアクティビティのデータと時間を伝えます。 これは、ログファイルの重要な要素の1つです。 これらのデータと時刻の値は、さまざまな形式で入力できることに注意してください。

以下に示すPythonスクリプトは、入力として生の日付時刻を取り、その出力として書式設定されたタイムスタンプを提供します。

このスクリプトでは、次の手順に従う必要があります-

  • 最初に、データのソースとデータ型とともに生データ値を取る引数を設定します。
  • 次に、異なる日付形式のデータに共通のインターフェースを提供するクラスを提供します。

Pythonコード

この目的のためにPythonコードを使用する方法を見てみましょう-

まず、次のPythonモジュールをインポートします-

from __future__ import print_function
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
from datetime import datetime as dt
from datetime import timedelta

さて、いつものように、コマンドラインハンドラーの引数を指定する必要があります。 ここでは、3つの引数を受け入れます。1つ目は処理する日付値、2つ目はその日付値のソース、3つ目はそのタイプです-

if __name__ == '__main__':
   parser = ArgumentParser('Timestamp Log-based artifact')
   parser.add_argument("date_value", help="Raw date value to parse")
   parser.add_argument(
      "source", help = "Source format of date",choices = ParseDate.get_supported_formats())
   parser.add_argument(
      "type", help = "Data type of input value",choices = ('number', 'hex'), default = 'int')

   args = parser.parse_args()
   date_parser = ParseDate(args.date_value, args.source, args.type)
   date_parser.run()
   print(date_parser.timestamp)

今、我々は、日付値、日付ソース、および値の型の引数を受け入れるクラスを定義する必要があります-

class ParseDate(object):
   def __init__(self, date_value, source, data_type):
      self.date_value = date_value
      self.source = source
      self.data_type = data_type
      self.timestamp = None

今、私たちはmain()メソッドのようにコントローラのように動作するメソッドを定義します-

def run(self):
   if self.source == 'unix-epoch':
      self.parse_unix_epoch()
   elif self.source == 'unix-epoch-ms':
      self.parse_unix_epoch(True)
   elif self.source == 'windows-filetime':
      self.parse_windows_filetime()
@classmethod
def get_supported_formats(cls):
   return ['unix-epoch', 'unix-epoch-ms', 'windows-filetime']

ここで、Unixエポック時間とFILETIMEをそれぞれ処理する2つのメソッドを定義する必要があります-

def parse_unix_epoch(self, milliseconds=False):
   if self.data_type == 'hex':
      conv_value = int(self.date_value)
      if milliseconds:
         conv_value = conv_value/1000.0
   elif self.data_type == 'number':
      conv_value = float(self.date_value)
      if milliseconds:
         conv_value = conv_value/1000.0
   else:
      print("Unsupported data type '{}' provided".format(self.data_type))
      sys.exit('1')
   ts = dt.fromtimestamp(conv_value)
   self.timestamp = ts.strftime('%Y-%m-%d %H:%M:%S.%f')
def parse_windows_filetime(self):
   if self.data_type == 'hex':
      microseconds = int(self.date_value, 16)/10.0
   elif self.data_type == 'number':
      microseconds = float(self.date_value)/10
   else:
      print("Unsupported data type '{}'   provided".format(self.data_type))
      sys.exit('1')
   ts = dt(1601, 1, 1) + timedelta(microseconds=microseconds)
   self.timestamp = ts.strftime('%Y-%m-%d %H:%M:%S.%f')

上記のスクリプトを実行した後、タイムスタンプを提供することにより、変換された値を読みやすい形式で取得できます。

Webサーバーログ

デジタルフォレンジックエキスパートの観点から見ると、Webサーバーログは、ユーザーおよび地理的な場所に関する情報とともに有用なユーザー統計を取得できるため、もう1つの重要な成果物です。 以下は、情報を簡単に分析するために、Webサーバーのログを処理した後、スプレッドシートを作成するPythonスクリプトです。

まず、次のPythonモジュールをインポートする必要があります-

from __future__ import print_function
from argparse import ArgumentParser, FileType

import re
import shlex
import logging
import sys
import csv

logger = logging.getLogger(__file__)

今、私たちはログから解析されるパターンを定義する必要があります-

iis_log_format = [
   ("date", re.compile(r"\d{4}-\d{2}-\d{2}")),
   ("time", re.compile(r"\d\d:\d\d:\d\d")),
   ("s-ip", re.compile(
      r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}")),
   ("cs-method", re.compile(
      r"(GET)|(POST)|(PUT)|(DELETE)|(OPTIONS)|(HEAD)|(CONNECT)")),
   ("cs-uri-stem", re.compile(r"([A-Za-z0-1/\.-]*)")),
   ("cs-uri-query", re.compile(r"([A-Za-z0-1/\.-]*)")),
   ("s-port", re.compile(r"\d*")),
   ("cs-username", re.compile(r"([A-Za-z0-1/\.-]*)")),
   ("c-ip", re.compile(
      r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}")),
   ("cs(User-Agent)", re.compile(r".*")),
   ("sc-status", re.compile(r"\d*")),
   ("sc-substatus", re.compile(r"\d*")),
   ("sc-win32-status", re.compile(r"\d*")),
   ("time-taken", re.compile(r"\d*"))]

次に、コマンドラインハンドラーの引数を指定します。 ここでは、2つの引数を受け入れます。1つ目は処理するIISログ、2つ目は目的のCSVファイルパスです。

if __name__ == '__main__':
   parser = ArgumentParser('Parsing Server Based Logs')
   parser.add_argument('iis_log', help = "Path to IIS Log",type = FileType('r'))
   parser.add_argument('csv_report', help = "Path to CSV report")
   parser.add_argument('-l', help = "Path to processing log",default=__name__ + '.log')
   args = parser.parse_args()
   logger.setLevel(logging.DEBUG)
   msg_fmt = logging.Formatter(
      "%(asctime)-15s %(funcName)-10s ""%(levelname)-8s %(message)s")

   strhndl = logging.StreamHandler(sys.stdout)
   strhndl.setFormatter(fmt = msg_fmt)
   fhndl = logging.FileHandler(args.log, mode = 'a')
   fhndl.setFormatter(fmt = msg_fmt)

   logger.addHandler(strhndl)
   logger.addHandler(fhndl)
   logger.info("Starting IIS Parsing ")
   logger.debug("Supplied arguments: {}".format(", ".join(sys.argv[1:])))
   logger.debug("System " + sys.platform)
   logger.debug("Version " + sys.version)
   main(args.iis_log, args.csv_report, logger)
   iologger.info("IIS Parsing Complete")

今、私たちはバルクログ情報のスクリプトを処理するmain()メソッドを定義する必要があります-

def main(iis_log, report_file, logger):
   parsed_logs = []

for raw_line in iis_log:
   line = raw_line.strip()
   log_entry = {}

if line.startswith("#") or len(line) == 0:
   continue

if '\"' in line:
   line_iter = shlex.shlex(line_iter)
else:
   line_iter = line.split(" ")
   for count, split_entry in enumerate(line_iter):
      col_name, col_pattern = iis_log_format[count]

      if col_pattern.match(split_entry):
         log_entry[col_name] = split_entry
else:
   logger.error("Unknown column pattern discovered. "
      "Line preserved in full below")
      logger.error("Unparsed Line: {}".format(line))
      parsed_logs.append(log_entry)

      logger.info("Parsed {} lines".format(len(parsed_logs)))
      cols = [x[0] for x in iis_log_format]

      logger.info("Creating report file: {}".format(report_file))
      write_csv(report_file, cols, parsed_logs)
      logger.info("Report created")

最後に、出力をスプレッドシートに書き込むメソッドを定義する必要があります-

def write_csv(outfile, fieldnames, data):
   with open(outfile, 'w', newline="") as open_outfile:
      csvfile = csv.DictWriter(open_outfile, fieldnames)
      csvfile.writeheader()
      csvfile.writerows(data)

上記のスクリプトを実行した後、スプレッドシートでWebサーバーベースのログを取得します。

YARAを使用して重要なファイルをスキャンする

YARA(Yet Another Recursive Algorithm)は、マルウェアの識別とインシデント対応のために設計されたパターンマッチングユーティリティです。 ファイルのスキャンにはYARAを使用します。 次のPythonスクリプトでは、YARAを使用します。

私たちは次のコマンドの助けを借りてYARAをインストールできます-

pip install YARA

YARAルールを使用してファイルをスキャンするための以下の手順に従うことができます-

  • まず、YARAルールを設定してコンパイルします
  • 次に、単一のファイルをスキャンしてから、ディレクトリを反復処理して個々のファイルを処理します。
  • 最後に、結果をCSVにエクスポートします。

Pythonコード

この目的のためにPythonコードを使用する方法を見てみましょう-

まず、次のPythonモジュールをインポートする必要があります-

from __future__ import print_function
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter

import os
import csv
import yara

次に、コマンドラインハンドラーの引数を指定します。 ここでは2つの引数を受け入れることに注意してください。1つ目はYARAルールへのパス、2つ目はスキャンするファイルです。

if __name__ == '__main__':
   parser = ArgumentParser('Scanning files by YARA')
   parser.add_argument(
      'yara_rules',help = "Path to Yara rule to scan with. May be file or folder path.")
   parser.add_argument('path_to_scan',help = "Path to file or folder to scan")
   parser.add_argument('--output',help = "Path to output a CSV report of scan results")
   args = parser.parse_args()
   main(args.yara_rules, args.path_to_scan, args.output)

ここで、スキャンするヤラルールとファイルへのパスを受け入れるmain()関数を定義します-

def main(yara_rules, path_to_scan, output):
   if os.path.isdir(yara_rules):
      yrules = yara.compile(yara_rules)
   else:
      yrules = yara.compile(filepath=yara_rules)
   if os.path.isdir(path_to_scan):
      match_info = process_directory(yrules, path_to_scan)
   else:
      match_info = process_file(yrules, path_to_scan)
   columns = ['rule_name', 'hit_value', 'hit_offset', 'file_name',
   'rule_string', 'rule_tag']

   if output is None:
      write_stdout(columns, match_info)
   else:
      write_csv(output, columns, match_info)

今、ディレクトリを反復処理し、さらに処理するために別のメソッドに結果を渡すメソッドを定義します-

def process_directory(yrules, folder_path):
   match_info = []
   for root, _, files in os.walk(folder_path):
      for entry in files:
         file_entry = os.path.join(root, entry)
         match_info += process_file(yrules, file_entry)
   return match_info

次に、2つの関数を定義します。 最初に* match()メソッドを使用してオブジェクトを *yrules し、別のメソッドはユーザーが出力ファイルを指定しない場合にその一致情報をコンソールに報告することに注意してください。 以下に示すコードを観察します-

def process_file(yrules, file_path):
   match = yrules.match(file_path)
   match_info = []

   for rule_set in match:
      for hit in rule_set.strings:
         match_info.append({
            'file_name': file_path,
            'rule_name': rule_set.rule,
            'rule_tag': ",".join(rule_set.tags),
            'hit_offset': hit[0],
            'rule_string': hit[1],
            'hit_value': hit[2]
         })
   return match_info
def write_stdout(columns, match_info):
   for entry in match_info:
      for col in columns:
         print("{}: {}".format(col, entry[col]))
   print("=" * 30)

最後に、以下に示すように、出力をCSVファイルに書き込むメソッドを定義します-

def write_csv(outfile, fieldnames, data):
   with open(outfile, 'w', newline="") as open_outfile:
      csvfile = csv.DictWriter(open_outfile, fieldnames)
      csvfile.writeheader()
      csvfile.writerows(data)

上記のスクリプトを正常に実行すると、コマンドラインで適切な引数を提供し、CSVレポートを生成できます。