Analyse scientifique des appareils mobiles numériques Python

Ce chapitre explique la criminalistique numérique Python sur les appareils mobiles et les concepts impliqués.

introduction

La criminalistique des appareils mobiles est la branche de la criminalistique numérique qui traite de l'acquisition et de l'analyse d'appareils mobiles pour récupérer des preuves numériques d'intérêt pour les enquêtes. Cette branche est différente de la criminalistique informatique car les appareils mobiles ont un système de communication intégré qui est utile pour fournir des informations utiles liées à l'emplacement.

Bien que l'utilisation des smartphones augmente de jour en jour dans la criminalistique numérique, elle est toujours considérée comme non standard en raison de son hétérogénéité. D'autre part, le matériel informatique, tel que le disque dur, est considéré comme standard et développé comme une discipline stable également. Dans l'industrie de la criminalistique numérique, il y a beaucoup de débats sur les techniques utilisées pour les appareils non standard, ayant des preuves transitoires, comme les smartphones.

Artefacts extractibles à partir d'appareils mobiles

Les appareils mobiles modernes possèdent beaucoup d'informations numériques par rapport aux téléphones plus anciens n'ayant qu'un journal des appels ou des messages SMS. Ainsi, les appareils mobiles peuvent fournir aux enquêteurs de nombreuses informations sur leur utilisateur. Certains artefacts qui peuvent être extraits d'appareils mobiles sont mentionnés ci-dessous -

  • Messages - Ce sont les artefacts utiles qui peuvent révéler l'état d'esprit du propriétaire et peuvent même donner des informations inconnues à l'enquêteur.

  • Location History- Les données d'historique de localisation sont un artefact utile qui peut être utilisé par les enquêteurs pour valider l'emplacement particulier d'une personne.

  • Applications Installed - En accédant au type d'applications installées, l'enquêteur obtient un aperçu des habitudes et de la pensée de l'utilisateur mobile.

Sources et traitement des preuves en Python

Les smartphones ont des bases de données SQLite et des fichiers PLIST comme principales sources de preuves. Dans cette section, nous allons traiter les sources des preuves en python.

Analyse des fichiers PLIST

Un PLIST (Property List) est un format flexible et pratique pour stocker des données d'application, en particulier sur les appareils iPhone. Il utilise l'extension.plist. Ce type de fichiers est utilisé pour stocker des informations sur les bundles et les applications. Il peut se présenter sous deux formats:XML et binary. Le code Python suivant ouvrira et lira le fichier PLIST. Notez qu'avant de procéder, nous devons créer notre propreInfo.plist fichier.

Tout d'abord, installez une bibliothèque tierce nommée biplist par la commande suivante -

Pip install biplist

Maintenant, importez quelques bibliothèques utiles pour traiter les fichiers plist -

import biplist
import os
import sys

Maintenant, utilisez la commande suivante sous la méthode main peut être utilisée pour lire le fichier plist dans une variable -

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)

Maintenant, nous pouvons soit lire les données sur la console, soit les imprimer directement, à partir de cette variable.

Bases de données SQLite

SQLite sert de référentiel de données principal sur les appareils mobiles. SQLite est une bibliothèque in-process qui implémente un moteur de base de données SQL transactionnel autonome, sans serveur, sans configuration. C'est une base de données, qui est configurée à zéro, vous n'avez pas besoin de la configurer dans votre système, contrairement aux autres bases de données.

Si vous êtes novice ou peu familiarisé avec les bases de données SQLite, vous pouvez suivre le lien De plus, vous pouvez suivre le lien au cas où vous le souhaiteriez entrer dans les détails de SQLite avec Python.

Au cours de la criminalistique mobile, nous pouvons interagir avec le sms.db fichier d'un appareil mobile et peut extraire des informations précieuses de messagetable. Python a une bibliothèque intégrée nomméesqlite3pour se connecter à la base de données SQLite. Vous pouvez importer le même avec la commande suivante -

import sqlite3

Maintenant, avec l'aide de la commande suivante, nous pouvons nous connecter à la base de données, disons sms.db en cas d'appareils mobiles -

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

Ici, C est l'objet curseur à l'aide duquel nous pouvons interagir avec la base de données.

Maintenant, supposons que si nous voulons exécuter une commande particulière, disons pour obtenir les détails du abc table, cela peut être fait à l'aide de la commande suivante -

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

Le résultat de la commande ci-dessus serait stocké dans le cursorobjet. De même, nous pouvons utiliserfetchall() méthode pour vider le résultat dans une variable que nous pouvons manipuler.

Nous pouvons utiliser la commande suivante pour obtenir les données des noms de colonne de la table de messages dans sms.db -

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

Notez qu'ici, nous utilisons la commande SQLite PRAGMA qui est une commande spéciale à utiliser pour contrôler diverses variables d'environnement et indicateurs d'état dans l'environnement SQLite. Dans la commande ci-dessus, lefetchall()La méthode renvoie un tuple de résultats. Le nom de chaque colonne est stocké dans le premier index de chaque tuple.

Maintenant, à l'aide de la commande suivante, nous pouvons interroger la table pour toutes ses données et les stocker dans la variable nommée data_msg -

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

La commande ci-dessus stockera les données dans la variable et nous pouvons également écrire les données ci-dessus dans un fichier CSV en utilisant csv.writer() méthode.

Sauvegardes iTunes

La criminalistique mobile iPhone peut être effectuée sur les sauvegardes effectuées par iTunes. Les examinateurs légistes s'appuient sur l'analyse des sauvegardes logiques iPhone acquises via iTunes. Le protocole AFC (Apple File Connection) est utilisé par iTunes pour effectuer la sauvegarde. En outre, le processus de sauvegarde ne modifie rien sur l'iPhone à l'exception des enregistrements de clé de dépôt.

Maintenant, la question se pose de savoir pourquoi il est important pour un expert en criminalistique numérique de comprendre les techniques sur les sauvegardes iTunes? C'est important au cas où nous aurions accès directement à l'ordinateur du suspect au lieu de l'iPhone, car lorsqu'un ordinateur est utilisé pour se synchroniser avec l'iPhone, la plupart des informations sur l'iPhone sont susceptibles d'être sauvegardées sur l'ordinateur.

Processus de sauvegarde et son emplacement

Chaque fois qu'un produit Apple est sauvegardé sur l'ordinateur, il est synchronisé avec iTunes et il y aura un dossier spécifique avec l'identifiant unique de l'appareil. Dans le dernier format de sauvegarde, les fichiers sont stockés dans des sous-dossiers contenant les deux premiers caractères hexadécimaux du nom de fichier. À partir de ces fichiers de sauvegarde, il existe des fichiers comme info.plist qui sont utiles avec la base de données nommée Manifest.db. Le tableau suivant montre les emplacements de sauvegarde, qui varient selon les systèmes d'exploitation des sauvegardes iTunes -

OS Emplacement de sauvegarde
Win7 C: \ Users \ [nom d'utilisateur] \ AppData \ Roaming \ AppleComputer \ MobileSync \ Backup \
MAC OS X ~ / Bibliothèque / Application Suport / MobileSync / Backup /

Pour traiter la sauvegarde iTunes avec Python, nous devons d'abord identifier toutes les sauvegardes dans l'emplacement de sauvegarde selon notre système d'exploitation. Ensuite, nous allons parcourir chaque sauvegarde et lire la base de données Manifest.db.

Maintenant, avec l'aide du code Python suivant, nous pouvons faire de même -

Tout d'abord, importez les bibliothèques nécessaires comme suit -

from __future__ import print_function
import argparse
import logging
import os

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

Maintenant, fournissez deux arguments de position à savoir INPUT_DIR et OUTPUT_DIR qui représente la sauvegarde iTunes et le dossier de sortie souhaité -

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()

Maintenant, configurez le journal comme suit -

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

Maintenant, configurez le format de message pour ce journal comme suit -

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)

La ligne de code suivante créera les dossiers nécessaires pour le répertoire de sortie souhaité en utilisant os.makedirs() fonction -

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

Maintenant, passez les répertoires d'entrée et de sortie fournis à la fonction main () comme suit -

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)

Maintenant écris main() fonction qui appellera en outre backup_summary() fonction pour identifier toutes les sauvegardes présentes dans le dossier d'entrée -

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

Maintenant, imprimez le résumé de chaque sauvegarde sur la console comme suit -

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

Maintenant, videz le contenu du fichier Manifest.db dans la variable nommée 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

Maintenant, définissons une fonction qui prendra le chemin du répertoire de la sauvegarde -

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

Maintenant, en utilisant SQLite3, nous allons nous connecter à la base de données par le curseur nommé 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)

Maintenant, définissez le create_files() méthode comme suit -

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)

Maintenant, parcourez chaque clé dans le db_items dictionnaire -

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)

Maintenant, utilisez shutil.copyfile() méthode pour copier le fichier sauvegardé comme suit -

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

Avec le script Python ci-dessus, nous pouvons obtenir la structure de fichiers de sauvegarde mise à jour dans notre dossier de sortie. On peut utiliserpycrypto bibliothèque python pour décrypter les sauvegardes.

Wifi

Les appareils mobiles peuvent être utilisés pour se connecter au monde extérieur en se connectant via des réseaux Wi-Fi disponibles partout. Parfois, l'appareil se connecte automatiquement à ces réseaux ouverts.

Dans le cas de l'iPhone, la liste des connexions Wi-Fi ouvertes avec lesquelles l'appareil s'est connecté est stockée dans un fichier PLIST nommé com.apple.wifi.plist. Ce fichier contiendra le SSID Wi-Fi, le BSSID et l'heure de connexion.

Nous devons extraire les détails Wi-Fi du rapport XML standard Cellebrite à l'aide de Python. Pour cela, nous devons utiliser l'API du Wireless Geographic Logging Engine (WIGLE), une plate-forme populaire qui peut être utilisée pour trouver l'emplacement d'un appareil à l'aide des noms de réseaux Wi-Fi.

Nous pouvons utiliser la bibliothèque Python nommée requestspour accéder à l'API depuis WIGLE. Il peut être installé comme suit -

pip install requests

API de WIGLE

Nous devons nous inscrire sur le site Web de WIGLE https://wigle.net/accountpour obtenir une API gratuite de WIGLE. Le script Python pour obtenir les informations sur la machine utilisateur et sa connexion via l'API de WIGEL est décrit ci-dessous -

Tout d'abord, importez les bibliothèques suivantes pour gérer différentes choses -

from __future__ import print_function

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

Maintenant, fournissez deux arguments de position à savoir INPUT_FILE et OUTPUT_CSV qui représentera respectivement le fichier d'entrée avec l'adresse MAC Wi-Fi et le fichier CSV de sortie souhaité -

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()

Les lignes de code suivantes vérifieront maintenant si le fichier d'entrée existe et est un fichier. Sinon, il quitte le script -

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(":")

Maintenant, passez l'argument à main comme suit -

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)

Maintenant, nous allons analyser le fichier XML comme suit -

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()

Maintenant, parcourez l'élément enfant de la racine comme suit -

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

Maintenant, nous allons vérifier que la chaîne 'ssid' est présente ou non dans le texte de la valeur -

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

Maintenant, nous devons ajouter BSSID, SSID et horodatage au dictionnaire wifi comme suit -

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

L'analyseur de texte qui est beaucoup plus simple que l'analyseur XML est montré ci-dessous -

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

Maintenant, utilisons le module de requêtes pour faire WIGLE APIappels et doivent passer au query_wigle() méthode -

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()

En fait, il y a une limite par jour pour les appels d'API WIGLE, si cette limite dépasse, il doit afficher une erreur comme suit -

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)

Maintenant, nous allons utiliser prep_output() méthode pour aplatir le dictionnaire en morceaux facilement inscriptibles -

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

Maintenant, accédez à toutes les données que nous avons collectées jusqu'à présent comme suit -

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

Maintenant, nous pouvons écrire la sortie dans un fichier CSV comme nous l'avons fait dans les scripts précédents de ce chapitre en utilisant write_csv() fonction.