Учебники

Python Digital Mobile Device Forensics

В этой главе описывается цифровая экспертиза Python для мобильных устройств и соответствующие концепции.

Вступление

Криминалистика мобильных устройств — это та отрасль криминалистической экспертизы, которая занимается сбором и анализом мобильных устройств для извлечения цифровых доказательств, представляющих интерес для следствия. Эта ветвь отличается от компьютерной криминалистики тем, что в мобильных устройствах имеется встроенная система связи, которая полезна для предоставления полезной информации, связанной с местонахождением.

Хотя использование смартфонов в цифровой криминалистике растет день ото дня, тем не менее, оно считается нестандартным из-за его неоднородности. С другой стороны, компьютерное оборудование, такое как жесткий диск, считается стандартным и развивается как стабильная дисциплина. В индустрии цифровой криминалистики много споров о методах, используемых для нестандартных устройств, имеющих временные доказательства, такие как смартфоны.

Извлекаемые из мобильных устройств артефакты

Современные мобильные устройства обладают большим количеством цифровой информации по сравнению со старыми телефонами, имеющими только журнал вызовов или SMS-сообщения. Таким образом, мобильные устройства могут предоставить следователям много информации о своем пользователе. Некоторые артефакты, которые могут быть извлечены с мобильных устройств, перечислены ниже:

  • Сообщения — это полезные артефакты, которые могут раскрыть душевное состояние владельца и даже дать некоторую ранее неизвестную информацию следователю.

  • История местоположений — данные истории местоположений являются полезным артефактом, который может использоваться следователями для проверки конкретного местоположения человека.

  • Установленные приложения. Получив доступ к установленным приложениям, исследователь получает представление о привычках и взглядах мобильного пользователя.

Сообщения — это полезные артефакты, которые могут раскрыть душевное состояние владельца и даже дать некоторую ранее неизвестную информацию следователю.

История местоположений — данные истории местоположений являются полезным артефактом, который может использоваться следователями для проверки конкретного местоположения человека.

Установленные приложения. Получив доступ к установленным приложениям, исследователь получает представление о привычках и взглядах мобильного пользователя.

Доказательства Источники и обработка в Python

Смартфоны имеют базы данных SQLite и файлы PLIST в качестве основных источников доказательств. В этом разделе мы собираемся обработать источники доказательств в Python.

Анализ файлов PLIST

PLIST (список свойств) — это гибкий и удобный формат для хранения данных приложения, особенно на устройствах iPhone. Он использует расширение .plist . Такие файлы используются для хранения информации о пакетах и ​​приложениях. Он может быть в двух форматах: XML и двоичный . Следующий код Python откроет и прочитает файл PLIST. Обратите внимание, что прежде чем приступить к этому, мы должны создать наш собственный файл Info.plist .

Сначала установите стороннюю библиотеку с именем biplist с помощью следующей команды —

Pip install biplist

Теперь импортируйте несколько полезных библиотек для обработки файлов plist —

import biplist
import os
import sys

Теперь используйте следующую команду в методе main для чтения файла 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, вы можете перейти по ссылке www.tutorialspoint.com/sqlite/index.htm. Кроме того, вы можете перейти по ссылке www.tutorialspoint.com/sqlite/sqlite_python.htm, если хотите углубиться в детали SQLite с помощью Python.

Во время мобильной криминалистики мы можем взаимодействовать с файлом sms.db мобильного устройства и извлекать ценную информацию из таблицы сообщений . Python имеет встроенную библиотеку с именем sqlite3 для подключения к базе данных SQLite. Вы можете импортировать то же самое с помощью следующей команды —

import sqlite3

Теперь с помощью следующей команды мы можем подключиться к базе данных, например, sms.db для мобильных устройств:

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

Здесь C — объект курсора, с помощью которого мы можем взаимодействовать с базой данных.

Теперь предположим, что если мы хотим выполнить определенную команду, скажем, чтобы получить подробности из таблицы abc , это можно сделать с помощью следующей команды:

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

Результат вышеупомянутой команды будет сохранен в объекте курсора . Точно так же мы можем использовать метод fetchall () для выгрузки результата в переменную, которой мы можем манипулировать.

Мы можем использовать следующую команду для получения данных имен столбцов таблицы сообщений в sms.db

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

Обратите внимание, что здесь мы используем команду SQLite PRAGMA, которая является специальной командой для управления различными переменными среды и флагами состояния в среде SQLite. В приведенной выше команде метод fetchall () возвращает набор результатов. Имя каждого столбца хранится в первом индексе каждого кортежа.

Теперь с помощью следующей команды мы можем запросить у таблицы все ее данные и сохранить ее в переменной с именем data_msg

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

Приведенная выше команда сохранит данные в переменной, и в дальнейшем мы также можем записать вышеуказанные данные в CSV-файл, используя метод csv.writer () .

резервные копии iTunes

Мобильная экспертиза iPhone может выполняться на резервных копиях iTunes. Судебно-медицинские эксперты полагаются на анализ логических резервных копий iPhone, полученных через iTunes. Протокол AFC (Apple File Connection) используется iTunes для резервного копирования. Кроме того, процесс резервного копирования не изменяет ничего на iPhone, кроме записей ключей условного депонирования.

Теперь возникает вопрос, почему для эксперта по цифровой экспертизе важно понимать методы резервного копирования iTunes? Это важно в случае, если мы получим доступ к компьютеру подозреваемого вместо iPhone напрямую, потому что, когда компьютер используется для синхронизации с iPhone, большая часть информации на iPhone, скорее всего, будет сохранена на компьютере.

Процесс резервного копирования и его расположение

Всякий раз, когда продукт Apple резервируется на компьютер, он синхронизируется с iTunes, и в нем будет определенная папка с уникальным идентификатором устройства. В последнем формате резервной копии файлы хранятся в подпапках, содержащих первые два шестнадцатеричных символа имени файла. Из этих файлов резервных копий есть некоторые файлы, такие как info.plist, которые полезны вместе с базой данных с именем Manifest.db. В следующей таблице показаны места резервного копирования, которые зависят от операционных систем резервных копий iTunes.

Операционные системы Местоположение резервной копии
Win7 C: \ Users \ [имя пользователя] \ AppData \ Roaming \ AppleComputer \ MobileSync \ Backup \
MAC OS X ~ / Библиотека / Поддержка приложений / MobileSync / Резервное копирование /

Для обработки резервной копии iTunes с помощью Python нам необходимо сначала определить все резервные копии в расположении резервной копии в соответствии с нашей операционной системой. Затем мы будем перебирать каждую резервную копию и читать базу данных 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__)

Теперь предоставьте два позиционных аргумента, а именно INPUT_DIR и OUTPUT_DIR, который представляет резервную копию iTunes и желаемую выходную папку —

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)

Теперь напишите функцию main (), которая будет дополнительно вызывать функцию backup_summary () для определения всех резервных копий, присутствующих во входной папке —

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, к которым подключено устройство, хранится в файле PLIST с именем com.apple.wifi.plist . Этот файл будет содержать SSID Wi-Fi, BSSID и время соединения.

Нам нужно извлечь детали Wi-Fi из стандартного отчета Cellebrite XML с использованием Python. Для этого нам нужно использовать API от Wireless Geographic Logging Engine (WIGLE), популярной платформы, которую можно использовать для поиска местоположения устройства с помощью имен сетей Wi-Fi.

Мы можем использовать библиотеку именованных запросов Python для доступа к API из WIGLE. Это может быть установлено следующим образом —

pip install requests

API от WIGLE

Нам нужно зарегистрироваться на сайте WIGLE https://wigle.net/account, чтобы получить бесплатный API от WIGLE. Сценарий Python для получения информации о пользовательском устройстве и его подключении через API WIGEL обсуждается ниже —

Сначала импортируйте следующие библиотеки для обработки разных вещей —

from __future__ import print_function

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

Теперь предоставьте два позиционных аргумента, а именно INPUT_FILE и OUTPUT_CSV, которые будут представлять входной файл с MAC-адресом Wi-Fi и желаемый выходной 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 следующим образом:

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:]

Теперь нам нужно добавить BSSID, SSID и метку времени в словарь wifi следующим образом:

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

Теперь давайте воспользуемся модулем запросов для выполнения вызовов API WIGLE и перейдем к методу 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, если этот предел превышает, то он должен показать ошибку следующим образом:

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"])

Теперь мы можем записать вывод в файл CSV, как мы делали в предыдущих сценариях в этой главе, используя функцию write_csv () .