Análisis forense de dispositivos móviles digitales de Python

Este capítulo explicará la ciencia forense digital de Python en dispositivos móviles y los conceptos involucrados.

Introducción

El análisis forense de dispositivos móviles es la rama del análisis forense digital que se ocupa de la adquisición y análisis de dispositivos móviles para recuperar evidencias digitales de interés investigativo. Esta rama es diferente de la informática forense porque los dispositivos móviles tienen un sistema de comunicación incorporado que es útil para proporcionar información útil relacionada con la ubicación.

Aunque el uso de teléfonos inteligentes está aumentando día a día en la ciencia forense digital, aún se considera que no es estándar debido a su heterogeneidad. Por otro lado, el hardware informático, como el disco duro, se considera estándar y también se ha desarrollado como una disciplina estable. En la industria forense digital, existe mucho debate sobre las técnicas utilizadas para dispositivos no estándares, que tienen evidencias transitorias, como los teléfonos inteligentes.

Artefactos extraíbles de dispositivos móviles

Los dispositivos móviles modernos poseen mucha información digital en comparación con los teléfonos más antiguos que solo tienen un registro de llamadas o mensajes SMS. Por lo tanto, los dispositivos móviles pueden proporcionar a los investigadores una gran cantidad de información sobre su usuario. Algunos artefactos que se pueden extraer de dispositivos móviles son los que se mencionan a continuación:

  • Messages - Estos son los artefactos útiles que pueden revelar el estado de ánimo del propietario e incluso pueden dar alguna información desconocida previa al investigador.

  • Location History- Los datos del historial de ubicaciones son un artefacto útil que los investigadores pueden utilizar para validar la ubicación particular de una persona.

  • Applications Installed - Al acceder al tipo de aplicaciones instaladas, el investigador obtiene información sobre los hábitos y el pensamiento del usuario móvil.

Fuentes de evidencia y procesamiento en Python

Los teléfonos inteligentes tienen bases de datos SQLite y archivos PLIST como las principales fuentes de evidencia. En esta sección vamos a procesar las fuentes de evidencias en Python.

Analizando archivos PLIST

PLIST (Lista de propiedades) es un formato flexible y conveniente para almacenar datos de aplicaciones, especialmente en dispositivos iPhone. Utiliza la extensión.plist. Este tipo de archivos se utilizan para almacenar información sobre paquetes y aplicaciones. Puede estar en dos formatos:XML y binary. El siguiente código de Python se abrirá y leerá el archivo PLIST. Tenga en cuenta que antes de continuar con esto, debemos crear nuestro propioInfo.plist archivo.

Primero, instale una biblioteca de terceros llamada biplist con el siguiente comando:

Pip install biplist

Ahora, importe algunas bibliotecas útiles para procesar archivos plist:

import biplist
import os
import sys

Ahora, use el siguiente comando en el método principal que se puede usar para leer el archivo plist en una 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)

Ahora, podemos leer los datos en la consola o imprimirlos directamente, desde esta variable.

Bases de datos SQLite

SQLite sirve como repositorio de datos principal en dispositivos móviles. SQLite una biblioteca en proceso que implementa un motor de base de datos transaccional SQL autónomo, sin servidor, sin configuración. Es una base de datos, que no está configurada, no necesita configurarla en su sistema, a diferencia de otras bases de datos.

Si es un novato o no está familiarizado con las bases de datos SQLite, puede seguir el enlace Además, puede seguir el enlace en caso de que desee entrar en detalles de SQLite con Python.

Durante el análisis forense móvil, podemos interactuar con el sms.db archivo de un dispositivo móvil y puede extraer información valiosa de messagemesa. Python tiene una biblioteca incorporada llamadasqlite3para conectarse con la base de datos SQLite. Puede importar lo mismo con el siguiente comando:

import sqlite3

Ahora, con la ayuda del siguiente comando, podemos conectarnos con la base de datos, digamos sms.db en el caso de dispositivos móviles -

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

Aquí, C es el objeto cursor con la ayuda del cual podemos interactuar con la base de datos.

Ahora, suponga que si queremos ejecutar un comando en particular, digamos para obtener los detalles del abc table, se puede hacer con la ayuda del siguiente comando:

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

El resultado del comando anterior se almacenaría en el cursorobjeto. Del mismo modo, podemos usarfetchall() método para volcar el resultado en una variable que podamos manipular.

Podemos usar el siguiente comando para obtener los datos de los nombres de columna de la tabla de mensajes en sms.db -

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

Observe que aquí estamos usando el comando SQLite PRAGMA, que es un comando especial que se usa para controlar varias variables ambientales y banderas de estado dentro del entorno SQLite. En el comando anterior, elfetchall()El método devuelve una tupla de resultados. El nombre de cada columna se almacena en el primer índice de cada tupla.

Ahora, con la ayuda del siguiente comando, podemos consultar la tabla para todos sus datos y almacenarlos en la variable llamada data_msg -

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

El comando anterior almacenará los datos en la variable y, además, también podemos escribir los datos anteriores en un archivo CSV usando csv.writer() método.

Copias de seguridad de iTunes

Los análisis forenses móviles de iPhone se pueden realizar en las copias de seguridad realizadas por iTunes. Los examinadores forenses confían en analizar las copias de seguridad lógicas del iPhone adquiridas a través de iTunes. ITunes utiliza el protocolo AFC (conexión de archivos de Apple) para realizar la copia de seguridad. Además, el proceso de copia de seguridad no modifica nada en el iPhone excepto los registros de la clave de custodia.

Ahora, surge la pregunta de por qué es importante que un experto forense digital comprenda las técnicas de las copias de seguridad de iTunes. Es importante en caso de que tengamos acceso a la computadora del sospechoso en lugar de al iPhone directamente porque cuando se usa una computadora para sincronizar con el iPhone, es probable que la mayor parte de la información del iPhone esté respaldada en la computadora.

Proceso de copia de seguridad y su ubicación

Siempre que se realiza una copia de seguridad de un producto de Apple en la computadora, se sincroniza con iTunes y habrá una carpeta específica con la identificación única del dispositivo. En el último formato de copia de seguridad, los archivos se almacenan en subcarpetas que contienen los dos primeros caracteres hexadecimales del nombre del archivo. De estos archivos de respaldo, hay algunos archivos como info.plist que son útiles junto con la base de datos llamada Manifest.db. La siguiente tabla muestra las ubicaciones de las copias de seguridad, que varían según los sistemas operativos de las copias de seguridad de iTunes:

SO Ubicación de respaldo
Win7 C: \ Users \ [nombre de usuario] \ AppData \ Roaming \ AppleComputer \ MobileSync \ Backup \
MAC OS X ~ / Biblioteca / Soporte de aplicaciones / MobileSync / Backup /

Para procesar la copia de seguridad de iTunes con Python, primero debemos identificar todas las copias de seguridad en la ubicación de la copia de seguridad según nuestro sistema operativo. Luego, iteraremos a través de cada copia de seguridad y leeremos la base de datos Manifest.db.

Ahora, con la ayuda de seguir el código de Python, podemos hacer lo mismo:

Primero, importe las bibliotecas necesarias de la siguiente manera:

from __future__ import print_function
import argparse
import logging
import os

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

Ahora, proporcione dos argumentos posicionales, a saber, INPUT_DIR y OUTPUT_DIR, que representan la copia de seguridad de iTunes y la carpeta de salida deseada:

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

Ahora, configure el registro de la siguiente manera:

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

Ahora, configure el formato de mensaje para este registro de la siguiente manera:

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 siguiente línea de código creará las carpetas necesarias para el directorio de salida deseado usando os.makedirs() función -

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

Ahora, pase los directorios de entrada y salida proporcionados a la función main () de la siguiente manera:

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)

Ahora escribe main() función que llamará más adelante backup_summary() función para identificar todas las copias de seguridad presentes en la carpeta de entrada -

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

Ahora, imprima el resumen de cada copia de seguridad en la consola de la siguiente manera:

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

Ahora, vuelque el contenido del archivo Manifest.db a la variable denominada 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

Ahora, definamos una función que tomará la ruta del directorio de la copia de seguridad:

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

Ahora, usando SQLite3 nos conectaremos a la base de datos mediante el cursor llamado 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)

Ahora, defina el create_files() método de la siguiente manera -

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)

Ahora, repita cada clave en el db_items diccionario -

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)

Ahora usa shutil.copyfile() método para copiar el archivo respaldado de la siguiente manera:

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

Con el script de Python anterior, podemos obtener la estructura de archivo de respaldo actualizada en nuestra carpeta de salida. Nosotros podemos usarpycrypto biblioteca de Python para descifrar las copias de seguridad.

Wifi

Los dispositivos móviles se pueden utilizar para conectarse al mundo exterior mediante la conexión a través de redes Wi-Fi que están disponibles en todas partes. A veces, el dispositivo se conecta a estas redes abiertas automáticamente.

En el caso de iPhone, la lista de conexiones Wi-Fi abiertas con las que se ha conectado el dispositivo se almacena en un archivo PLIST llamado com.apple.wifi.plist. Este archivo contendrá el SSID de Wi-Fi, BSSID y el tiempo de conexión.

Necesitamos extraer detalles de Wi-Fi del informe XML estándar de Cellebrite usando Python. Para esto, necesitamos usar API de Wireless Geographic Logging Engine (WIGLE), una plataforma popular que se puede usar para encontrar la ubicación de un dispositivo usando los nombres de redes Wi-Fi.

Podemos usar la biblioteca de Python llamada requestspara acceder a la API de WIGLE. Se puede instalar de la siguiente manera:

pip install requests

API de WIGLE

Necesitamos registrarnos en el sitio web de WIGLE https://wigle.net/accountpara obtener una API gratuita de WIGLE. El script de Python para obtener la información sobre el dispositivo del usuario y su conexión a través de la API de WIGEL se analiza a continuación:

Primero, importe las siguientes bibliotecas para manejar diferentes cosas:

from __future__ import print_function

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

Ahora, proporcione dos argumentos posicionales a saber INPUT_FILE y OUTPUT_CSV que representará el archivo de entrada con la dirección MAC de Wi-Fi y el archivo CSV de salida deseado respectivamente -

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

Ahora, las siguientes líneas de código comprobarán si el archivo de entrada existe y es un archivo. Si no es así, sale del guión -

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

Ahora, pase el argumento a main de la siguiente manera:

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)

Ahora, analizaremos el archivo XML de la siguiente manera:

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

Ahora, itere a través del elemento hijo de la raíz de la siguiente manera:

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

Ahora, comprobaremos que la cadena 'ssid' esté presente en el texto del valor o no -

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

Ahora, necesitamos agregar BSSID, SSID y marca de tiempo al diccionario wifi de la siguiente manera:

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

El analizador de texto, que es mucho más simple que el analizador XML, se muestra a continuación:

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

Ahora, usemos el módulo de solicitudes para hacer WIGLE APIllamadas y necesita pasar a la query_wigle() método -

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 realidad, hay un límite por día para las llamadas a la API de WIGLE, si ese límite excede, debe mostrar un error de la siguiente manera:

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)

Ahora usaremos prep_output() método para aplanar el diccionario en fragmentos fáciles de escribir -

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

Ahora, acceda a todos los datos que hemos recopilado hasta ahora de la siguiente manera:

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

Ahora, podemos escribir la salida en un archivo CSV como lo hemos hecho en scripts anteriores en este capítulo usando write_csv() función.