martedì 5 marzo 2024

Published marzo 05, 2024 by Django Faiola with 0 comment

iOS Burner - Cache.db

Introduzione

Questa seconda parte è un approfondimento dell'analisi di Burner, la cache. Se non l'hai ancora fatto ti consiglio di leggere la prima parte iOS Burner sull'analisi del database "Phoenix.sqlite". 

App Store: https://apps.apple.com/us/app/burner-private-phone-line/id505800761

Percorsi

Di seguito lo stralcio di Burner del poster della SANS “iOS Third-Party Apps Forensics Reference Guide Poster” con le informazioni più rilevanti per l’analisi dell’app.

Analisi degli artefatti

Per lo studio di questa app ho utilizzato una delle immagini pubbliche di iOS note nel settore e in particolare quella di Josh Hickman iOS 15.3.1 disponibile su DigitalCorporaLa versione di Burner è 5.3.8.

Cache.db

Questo database SQLite tiene traccia dei dati che l'app ha ricevuto da fonti esterne come un server o internet, una cache per velocizzare il caricamento se necessario. Tre sono le tabelle principali di interesse:

  1. cfurl_cache_response: contiene i dati e l'ora della richiesta;
  2. cfurl_cache_blob_data: contiene i dati BLOB (Binary Large Object) con la risposta del server;
  3. cfurl_cache_receiver_data: contiene i dati ricevuti dal server in risposta alla richiesta al server tramite la tabella cfurl_cache_response.

Mettere in relazione queste tabelle è importante in quanto può fornire informazioni che non sono memorizzate altrove; ad esempio i messaggi vocali riprodotti. Se la dimensione dei dati è di 4096 byte o superiore verranno memorizzati su file (cfurl_cache_response.isDataOnFs=1) con il nome dell'UUID assegnato al campo cfurl_cache_receiver_data.receiver_data, altrimenti verranno memorizzati nel database (cfurl_cache_response.isDataOnFs=0) come BLOB.

Per prima cosa bisogna ricostruire le relazioni delle tabelle di questo database SQLite. 

SELECT
  cr.request_key AS "URL",
  datetime(strftime("%s", cr.time_stamp), 'unixepoch') AS "timestamp",
  crd.isDataOnFS,
  crd.receiver_data AS "data"
FROM cfurl_cache_response AS "cr"
LEFT JOIN cfurl_cache_receiver_data AS "crd" ON (cr.entry_ID = crd.entry_ID)

Non entro nel dettaglio delle tabelle, quello che maggiormente interessa sono i campi:

  • cfurl_cache_response.request_key (URL): è la stringa di richiesta al server;
  • cfurl_cache_receiver_data.isDataOnFS (isDataOnFS): indica che i dati sono 1=esterni su file, 0=incorporati nel campo data;
  • cfurl_cache_receiver_data.receiver_data (data): contiene i dati ricevuti o il nome del file presente nella sottocartella "fsCachedData". 

Allegati

Da un'ispezione visiva degli URL è possibile identificare due domini di interesse; quello per l'archiviazione delle risorse esterne "s3.amazonaws.com" (Amazon S3) e l'altro per l'interazione dell'app con il server "phoenix.burnerapp.com" (Burner).

Per quanto riguarda Amazon S3, il primo tratto del percorso dell'URL identifica il tipo di risorsa; /burner-mms/ per le immagini e /burner-voicemail/ per i messaggi vocali. 

SELECT
    cr.request_key AS "URL",
    datetime(strftime("%s", cr.time_stamp), 'unixepoch') AS "timestamp",
    crd.isDataOnFS,
    crd.receiver_data AS "data"
FROM cfurl_cache_response AS "cr"
LEFT JOIN cfurl_cache_receiver_data AS "crd" ON (cr.entry_ID = crd.entry_ID)
WHERE cr.request_key like "https://s3.amazonaws.com/burner-%"

Questa query permette di creare una mappatura tra il campo receiver_data (nome del file presente nella sottocartella "fsCachedData" o BLOB) e l'URL della risorsa che servirà in seguito per associare l'allegato al messaggio ad esempio.

Messaggi

Questo è il contenuto del file "fsCachedData/34A0C948-4FFD-4FA3-B5DC-5DB8ADBD8B0D" in dfDataViewer (il mio visualizzatore di (B)Plist, JSON, (B)XML, ASN.1, etc. ancora in fase di sviluppo):

Identificare gli allegati presenti nel JSON è semplice: nell'object 6 (messaggio), con l' assetUrl="https://s3.amazonaws.com/burner-mms/prod/9ebdd109-01bf-46b9-af6a-b59f671b8c85.jpg" si cerca la corrispondenza dell'URL nella query dei media sopra. A questo URL corrisponde il file (isDataOnFS=1) "54F501A4-8AB8-4BCE-8681-163E7559ADA5".

Ora bisogna analizzare la struttura del JSON dei messaggi. In genere come ho detto sopra i messaggi sono presenti o in file esterni o come dati incorporati nel campo receiver_data. In entrambi i casi la root del JSON può essere o un array (1 o più messaggi) o un object (1 messaggio).

L'immagine della struttura del JSON sopra riportata è riferita al file "fsCachedData/34A0C948-4FFD-4FA3-B5DC-5DB8ADBD8B0D" e come si può osservare per l'object 6 le proprietà/valori di interesse per la ricostruzione del messaggio sono più o meno le stesse di quelle del database "Phoenix.sqlite" salvo qualche eccezione; per esempio i timestamp sono nel formato Unix Epoch (millisecondi).

Prima di procedere alla ricostruzione del messaggio è necessario identificare in "Cache.db" quali sono i record che contengono i messaggi. Da una ispezione visiva dei valori in data e dall'analisi di tutti i file esterni, il tutto riconduce agli URL con percorso iniziale "https://phoenix.burnerapp.com" e che contengono "/user/<USER UUID>/messages" (POST) o "/user/<USER UUID>/messages?" (GET). 

SELECT
    crd.isDataOnFS AS "isDataOnFS",
    crd.receiver_data
FROM cfurl_cache_response AS "cr"
LEFT JOIN cfurl_cache_receiver_data AS "crd" ON (cr.entry_ID = crd.entry_ID)
WHERE cr.request_key REGEXP "https://phoenix\.burnerapp\.com(/v\d)?/user/[a-zA-Z0-9-]{36}/messages($|\?.*contactPhoneNumber=.+)"

E' arrivato il momento di ricostruire il messaggio 6:

  • dateCreated1683568403040 (08 maggio 2023 17:53:23+00:00);
  • direction: 2 (uscita);
  • read: true (letto);
  • contactPhoneNumber: +19198887xxx (destinatario);
  • message: ;
  • messageType: 2 (Testo/Immagine);
  • assetUrl: https://s3.amazonaws.com/burner-mms/prod/9ebdd109-01bf-46b9-af6a-b59f671b8c85.jpg;
  • voiceUrl: null;
  • burnerId: 3419959a-a3aa-4cd0-b5e1-eed24fa52xxx;
  • id: a0732e75-a4de-45b9-a320-b18af9002xxx.
per completare il messaggio, l'allegato in "fsCachedData\54F501A4-8AB8-4BCE-8681-163E7559ADA5".

Numeri Burner

SELECT
crd.isDataOnFS AS "isDataOnFS",
crd.receiver_data
FROM cfurl_cache_response AS "cr"
LEFT JOIN cfurl_cache_receiver_data AS "crd" ON (cr.entry_ID = crd.entry_ID)
WHERE cr.request_key REGEXP "https://phoenix\.burnerapp\.com(/v\d)?/user/[a-zA-Z0-9-]{36}/burners(/[a-zA-Z0-9-]{36})?$"

Un solo numero burner:

  • settings.name: My New Burner;
  • phoneNumber: +18285205xxx;
  • dateCreated: 1683566577088 (08 maggio 2023 17:22:57+00:00);
  • expirationDate: 1686158577088 (07 giugno 2023 17:22:57+00:00);
  • settings.notificationsEnabled: true (On);
  • settings.voipEnabled: false (Standard Voice);
  • settings.autoReplyMessage.active: false (Off);
  • settings.autoReplyMessage.text: I'm away from my phone. I'll get back to you ASAP.;
  • entitlements.remainingMinutes: 48;
  • entitlements.totalMinutes: 50;
  • entitlements.remainingTexts : 87;
  • entitlements.totalTexts : 100;
  • id: 3419959a-a3aa-4cd0-b5e1-eed24fa52xxx;

Contatti

SELECT
    crd.isDataOnFS AS "isDataOnFS",
    crd.receiver_data
FROM cfurl_cache_response AS "cr"
LEFT JOIN cfurl_cache_receiver_data AS "crd" ON (cr.entry_ID = crd.entry_ID)
WHERE cr.request_key REGEXP "https://phoenix\.burnerapp\.com(/v\d)?/user/[a-zA-Z0-9-]{36}/contacts\?.+"

Due contatti. Object 0 (primo contatto):

  • dateCreated: 1613670031089 (18 febbraio 2021 17:40:31+00:00);
  • name: This Is DFIR Two;
  • phoneNumber: +19198887xxx;
  • notes: (0 items);
  • verified: false (No);
  • blocked: false (No);
  • muted: false (No);
  • images: (0 items)
  • id: 1955458a-57c4-4b4e-9105-205f58f9fxxx.

Account

SELECT
crd.isDataOnFS AS "isDataOnFS",
crd.receiver_data
FROM cfurl_cache_response AS "cr"
LEFT JOIN cfurl_cache_receiver_data AS "crd" ON (cr.entry_ID = crd.entry_ID)
WHERE cr.request_key REGEXP "https://phoenix\.burnerapp\.com(/v\d)?(/register(/phone)?$|/user/[a-zA-Z0-9-]{36}/token)"


  • dateCreated: 1586047847213 (05 aprile 2020 00:50:47+00:00);
  • phoneNumber: +19195794xxx;
  • carrierName: T-Mobile USA, Inc.;
  • countryCode: US;
  • appVersion: 5.3.8;
  • token: eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJodHRwczovL2lkZW50a...
  • id: 21d2ca9c-aba1-4ada-b7f5-23b0d97acxxx.

iLEAPP

Al fine di facilitare l'analisi di Burner per iOS ho sviluppato un plugin per iLEAPP (iOS Logs, Events, And Plists Parser) di Alexis Brignoni. Alla data di pubblicazione il plugin è in “pull request” e comunque disponibile sul mio GitHub.

Di seguito alcuni screenshot del report di Burner:

Read More
Published marzo 05, 2024 by Django Faiola with 0 comment

iOS Burner

Introduzione

Buner è un’applicazione mobile per iOS e Android sviluppata da Ad Hoc Labs, Inc. che consente agli utenti di mantenere privato il proprio numero (SIM) di telefono acquistando numeri di telefono usa e getta virtuali per scopi come annunci online, gioco d’azzardo, chat di incontri, privacy e altro.

Basta fornire il numero burner e per esempio una chiamata in arrivo su questo numero viene reindirizzata in modo trasparente al numero personale (SIM) a meno che non si decida di bloccare il contatto. Per maggiori dettagli sulle funzionalità dell'app consulta https://www.burnerapp.com.

App Store: https://apps.apple.com/us/app/burner-private-phone-line/id505800761

Percorsi

Di seguito lo stralcio di Burner del poster della SANS “iOS Third-Party Apps Forensics Reference Guide Poster” con le informazioni più rilevanti per l’analisi dell’app.

Analisi degli artefatti

Per lo studio di questa app ho utilizzato una delle immagini pubbliche di iOS note nel settore e in particolare quella di Josh Hickman iOS 15.3.1 disponibile su DigitalCorporaLa versione di Burner è 5.3.8.

Phoenix.sqlite è il database principale che contiene le tabelle degli account, contatti, numeri burner, messaggi e chiamate, etc. La tabella "ZBURNER" per esempio contiene solo i numeri burner attivi; infatti quando l’abbonamento scade il numero viene "bruciato" e rimosso da questa tabella. Tutti i valori data/ora (timestamp) sono riferiti al tempo UTC e nel formato Mac absolute time che conta il numero di secondi dal 1 gennaio del 2001.

Di seguito un dettaglio non esaustivo delle tabelle di interesse:

ZBURNER (numeri burner)

  • Z_PK: chiave primaria;
  • ZDATECREATED: data e ora di creazione del numero;
  • ZEXPIRATIONDATE: data e ora di scadenza dell’abbonamento;
  • ZNAME: nome visualizzato;
  • ZPHONENUMBER: numero burner;
  • ZBURNERID: identificatore del burner;
  • ZREMAININGMINUTES: minuti di conversazione vocale residui;
  • ZTOTALMINUTES: minuti di conversazione totali (abbonamento);
  • ZREMAININGTEXTS: numero di messaggi di testo residui;
  • ZTOTALTEXTS: numero di messaggi di testo totali (abbonamento);
  • ZAUTOREPLYACTIVE: abilita/disabilita il messaggio di testo di risposta automatica (0=Off);
  • ZAUTOREPLYTEXT: messaggio di testo della risposta automatica;
  • ZUSER: chiave esterna dell’account associato al numero (Z_PK della tabella ZUSER);
  • ZCALLERIDENABLED: mostra le chiamate in entrata come numero  burner (0=Burner Number) o come numero di telefono del chiamante (1=Caller Number);
  • ZNOTIFICATIONS: abilita/disabilita le notifiche (1=On);
  • ZRINGER: abilita/disabilita la suoneria (1=On);
  • ZUSESIP: In-App Calling (VoIP); 0=usa il numero burner per effettuare o ricevere chiamate "VoIP" o 1=chiamate telefoniche "Voice";
  • ZVOICEMAILURL: URL del messaggio vocale di benvenuto “richiesta di lasciare un messaggio vocale” personalizzato;
  • ZIMAGE: contiene l’immagine del profilo (offset: 0x02).
SELECT
  B.Z_PK AS "B_PK",
  U.Z_PK AS "U_PK",
  substr(B.ZIMAGE, 2, length(B.ZIMAGE) - 2) AS "image",
  B.ZPHONENUMBER AS "burnerNumber",
  B.ZNAME AS "displayName",
  datetime(B.ZDATECREATED + 978307200, 'unixepoch') AS "dateCreated",
  datetime(B.ZEXPIRATIONDATE + 978307200, 'unixepoch') AS "expirationDate",
  CASE B.ZNOTIFICATIONS
WHEN 1 THEN "On"
ELSE "Off"
  END AS "notifications",
  CASE B.ZCALLERIDENABLED 
WHEN 0 THEN "Burner Number"
ELSE "Caller Number"
  END AS "inboundCallerId",
  CASE B.ZUSESIP
WHEN 0 THEN "Standard Voice"
ELSE "VoIP"
  END AS "VoIPinAppCalling",
  CASE B.ZAUTOREPLYACTIVE
WHEN 0 THEN "No"
ELSE "Yes"
  END AS "autoReplyActive",
  B.ZAUTOREPLYTEXT AS "autoReplyText",
  coalesce(B.ZREMAININGMINUTES, "0") || "/" || coalesce(B.ZTOTALMINUTES, "0") AS "minutes",
  coalesce(B.ZREMAININGTEXTS, "0") || "/" || coalesce(B.ZTOTALTEXTS, "0") AS "texts",
  U.ZPHONENUMBER AS "mobilePhone",
  U.ZUSERID AS "userId",
  B.ZBURNERID AS "burnerId"
FROM ZBURNER AS "B"
LEFT JOIN ZUSER AS "U" ON (B.ZUSER = U.Z_PK)

ZUSER (account)

  • Z_PK: chiave primaria;
  • ZDNDENABLED: abilita/disabilita "Non disturbare" (1=On);
  • ZTOTALNUMBERBURNERS: numero totale di burner creati;
  • ZDATECREATED: data e ora di creazione dell’account;
  • ZCOUNTRYCODE: codice paese;
  • ZNAME: nome visualizzato;
  • ZPHONENUMBER: numero di telefono;
  • ZUSERID: identificatore utente.
SELECT
  U.Z_PK AS "U_PK",
  B.B_PK,
  datetime(U.ZDATECREATED + 978307200, 'unixepoch') AS "dateCreated",
  U.ZPHONENUMBER AS "mobilePhone",
  U.ZCOUNTRYCODE AS "countryCode",
  (coalesce(B.nBurners, "0") || "/" || U.ZTOTALNUMBERBURNERS) AS "nBurners",
  B.burnerNames,
  B.burnerIds,
  CASE U.ZDNDENABLED
    WHEN 1 THEN "On"
    ELSE "Off"
  END AS "dndEnabled",
  U.ZVOICEMAILURL AS "voicemailUrl",
  U.ZUSERID AS "userId"
FROM ZUSER AS "U"
LEFT JOIN (
  SELECT 
    BU.ZUSER,
group_concat(BU.Z_PK, char(29)) AS "B_PK",
count(BU.ZUSER) AS "nBurners",
group_concat(IIF(BU.ZNAME NOT NULL, BU.ZPHONENUMBER || " (" || BU.ZNAME || ")", BU.ZPHONENUMBER), char(29)) AS "burnerNames",
group_concat(BU.ZBURNERID, char(29)) AS "burnerIds"
  FROM ZBURNER AS "BU" 
  GROUP BY BU.ZUSER  
) AS "B" ON (U.Z_PK = B.ZUSER)

ZCONTACT (contatti)

  • Z_PK: chiave primaria;
  • ZBLOCKED: 1=contatto bloccato;
  • ZMUTED: 1=modalità silenziosa;
  • ZVERIFIED: 1=contatto verificato;
  • ZCONTACTID: identificatore del contatto;
  • ZFRIENDLYPHONENUMBER: numero di telefono nel formato “numero telefonico”;
  • ZIMAGEURL: URL dell’immagine del contatto;
  • ZNAME: nome visualizzato;
  • ZNOTE: chiave esterna delle note (Z_PK della tabella ZCONTACTNOTE);
  • ZPHONENUMBER: numero di telefono;
  • ZTHUMBNAILURL: URL della miniatura dell’immagine del contatto;
  • ZIMAGE: contiene l’immagine del contatto (offset: 0x02);
  • ZTHUMBNAIL: contiene la miniatura dell’immagine del contatto (offset: 0x02).

ZCONTACTPHONENUMBER (altri numeri di telefono)

  • Z_PK: chiave primaria;
  • ZCONTACT: chiave esterna del contatto associato al numero di telefono (Z_PK della tabella ZCONTACT);
  • ZPHONENUMBER: numero di telefono;
  • ZPHONENUMBERLABEL: etichetta, esempio nome dell’app associata al numero "TextNow".

ZCONTACTNOTE (note)

  • Z_PK: chiave primaria;
  • ZCONTACT: chiave esterna del contatto associato alla nota (Z_PK della tabella ZCONTACT);
  • ZNOTEVALUE: la nota.
SELECT
  C.Z_PK AS "C_PK",
  CPN.CPN_PK,
  CN.CN_PK,
  C.ZPHONENUMBER AS "phoneNumber",
  C.ZNAME AS "name",
  CPN.otherPhones,
  CN.notes,
  substr(C.ZIMAGE, 2, length(C.ZIMAGE) - 2) AS "image",
  substr(C.ZTHUMBNAIL, 2, length(C.ZTHUMBNAIL) - 2) AS "thumbnail",
  C.ZIMAGEURL AS "imageUrl",
  C.ZTHUMBNAILURL AS "thumbnailUrl",
  C.ZVERIFIED AS "verified",
  C.ZBLOCKED AS "blocked",
  C.ZMUTED AS "muted",
  C.ZCONTACTID AS "contactId"
FROM ZCONTACT AS "C"
LEFT JOIN (
  SELECT 
PN.ZCONTACT,
    group_concat(coalesce(PN.Z_PK, ""), char(29)) AS "CPN_PK",
group_concat(IIF(PN.ZPHONENUMBERLABEL IS NOT NULL, "(" || PN.ZPHONENUMBERLABEL || ") " || PN.ZPHONENUMBER, PN.ZPHONENUMBER), char(29)) AS "otherPhones"
  FROM ZCONTACTPHONENUMBER AS "PN"
  GROUP BY PN.ZCONTACT
) AS "CPN" ON (C.Z_PK = CPN.ZCONTACT)
LEFT JOIN (
  SELECT
    N.ZCONTACT,
    group_concat(coalesce(N.Z_PK, ""), char(29)) AS "CN_PK",
group_concat(coalesce(N.ZNOTEVALUE, ""), char(29)) AS "notes"
  FROM ZCONTACTNOTE AS "N"
  GROUP BY N.ZCONTACT
) AS "CN" ON (C.Z_PK = CN.ZCONTACT)

ZMESSAGE (messaggi/chiamate)

  • Z_PK: chiave primaria;
  • ZDIRECTION: direzione del messaggio/chiamata 1=entrata, 2=uscita;
  • ZREAD: 1=letto;
  • ZSTATE: 3=completo, 4=chiamata persa, 5=chiamata persa con messaggio vocale;
  • ZTYPE: tipo di messaggio 1=chiamata o messaggio vocale, 2=testo o immagine;
  • ZMESSAGETHREAD: chiave esterna della conversazione associata al messaggio (Z_PK della tabella ZMESSAGETHREAD);
  • ZDATECREATED: data e ora di creazione del messaggio;
  • ZLASTUPDATED: data e ora dell’ultimo aggiornamento;
  • ZASSETURL: URL della risorsa (es: immagine);
  • ZBODY: testo del messaggio;
  • ZBURNERID: identificatore del burner;
  • ZCONTACTPHONENUMBER: numero di telefono dell'interlocutore;
  • ZLOCALASSETURL: percorso locale (dispositivo) dell’immagine;
  • ZLOCALTHUMBNAILURL: percorso locale (dispositivo) della miniatura dell’immagine;
  • ZMESSAGEID: identificatore del messaggio;
  • ZVOICEMAILURL: URL del messaggio vocale.

ZMESSAGETHREAD (conversazioni)

  • Z_PK: chiave primaria;
  • ZDIRECTION: direzione del messaggio/chiamata 1=entrata, 2=uscita;
  • ZREAD: 1=letto;
  • ZSTATE: 3=completo, 4=chiamata persa, 5=chiamata persa con messaggio vocale;
  • ZTYPE: tipo di messaggio 1=chiamata o messaggio vocale, 2=testo o immagine;
  • ZBURNER: chiave esterna del burner associato alla conversazione (Z_PK della tabella ZBURNER);
  • ZCONTACT: chiave esterna del contatto associato al numero di telefono (Z_PK della tabella ZCONTACT);
  • ZDATECREATED: data e ora di creazione della conversazione;
  • ZLASTUPDATEDDATE: data e ora dell'ultimo aggiornamento;
  • ZASSETURL: URL della risorsa (es: immagine);
  • ZBODY: testo dell'ultimo messaggio;
  • ZCONTACTPHONENUMBER: numero di telefono dell'interlocutore;
  • ZMESSAGETHREADID: identificativo della conversazione.
SELECT
  MT.Z_PK AS "MT_PK",
  C.Z_PK AS "C_PK",
  M.Z_PK AS "M_PK",
  B.Z_PK AS "B_PK",
  IIF(C.ZNAME IS NOT NULL, C.ZPHONENUMBER || " (" || C.ZNAME || ")", C.ZPHONENUMBER) AS "thread",
  datetime(M.ZDATECREATED + 978307200, 'unixepoch') AS "dateCreated",
  IIF(M.ZDIRECTION = 1, "Incoming", "Outgoing") AS "direction",
  IIF(M.ZREAD = 1, "Read", "Not read") AS "read",
  IIF (M.ZDIRECTION = 1, 
    IIF(C.ZNAME IS NOT NULL, C.ZPHONENUMBER || " (" || C.ZNAME || ")", C.ZPHONENUMBER),
IIF(B.ZNAME IS NOT NULL, B.ZPHONENUMBER || " (" || B.ZNAME || ")", B.ZPHONENUMBER)
  ) AS "sender",
  IIF (M.ZDIRECTION = 2, 
IIF(C.ZNAME IS NOT NULL, C.ZPHONENUMBER || " (" || C.ZNAME || ")", C.ZPHONENUMBER),
    IIF(B.ZNAME IS NOT NULL, B.ZPHONENUMBER || " (" || B.ZNAME || ")", B.ZPHONENUMBER) 
  ) AS "recipient",
  CASE 
WHEN (M.ZDIRECTION = 1) AND (M.ZSTATE = 3) THEN "Completed incoming call"
WHEN (M.ZDIRECTION = 2) AND (M.ZSTATE = 3) THEN "Completed outgoing call"
WHEN (M.ZDIRECTION = 1) AND (M.ZSTATE = 4) THEN "Missed incoming call"
WHEN (M.ZDIRECTION = 2) AND (M.ZSTATE = 4) THEN "Missed outgoing call"
WHEN (M.ZDIRECTION = 1) AND (M.ZSTATE = 5) THEN "Missed incoming call with voicemail"
WHEN (M.ZDIRECTION = 2) AND (M.ZSTATE = 5) THEN "Missed outgoing call with voicemail"
ELSE M.ZBODY
  END AS "message",
  CASE 
    WHEN (M.ZTYPE = 1) AND (M.ZSTATE in (3, 4)) THEN "Call"
WHEN (M.ZTYPE = 1) AND (M.ZSTATE = 5) THEN "Voicemail"
WHEN (M.ZTYPE = 2) AND (coalesce(M.ZLOCALASSETURL, M.ZLOCALTHUMBNAILURL) IS NULL) THEN "Text"
WHEN (M.ZTYPE = 2) THEN "Picture"
ELSE M.ZTYPE
  END AS "mType",
  M.ZLOCALASSETURL AS "localAsset",
  M.ZLOCALTHUMBNAILURL AS "localThumbnail",
  M.ZASSETURL AS "mediaUrl",
  M.ZVOICEMAILURL AS "voiceUrl",
  M.ZMESSAGEID AS "messageId",
  M.ZBURNERID AS "burnerId",
  MT.ZMESSAGETHREADID AS "threadId"
FROM ZMESSAGE AS "M"
LEFT JOIN ZBURNER AS "B" ON (M.ZBURNERID = B.ZBURNERID)
LEFT JOIN ZMESSAGETHREAD AS "MT" ON (M.ZMESSAGETHREAD = MT.Z_PK) OR (M.ZMESSAGETHREAD IS NULL AND M.ZBURNERID = MT.ZBURNERID AND M.ZCONTACTPHONENUMBER = MT.ZCONTACTPHONENUMBER)
LEFT JOIN ZCONTACT AS "C" ON (MT.ZCONTACT = C.Z_PK) OR (MT.ZCONTACT IS NULL AND M.ZCONTACTPHONENUMBER = C.ZPHONENUMBER)

iLEAPP

Al fine di facilitare l'analisi di Burner per iOS ho sviluppato un plugin per iLEAPP (iOS Logs, Events, And Plists Parser) di Alexis Brignoni. Alla data di pubblicazione il plugin è in “pull request” e comunque disponibile sul mio GitHub.

Di seguito alcuni screenshot del report di Burner:


Read More

giovedì 8 febbraio 2024

Published febbraio 08, 2024 by Django Faiola with 0 comment

iOS WAZE

Introduzione

Waze è un’applicazione mobile di navigazione stradale gratuita per iOS e Android basata sul concetto di crowdsourcing, inizialmente sviluppata dalla start-up israeliana Waze Mobile e dal 2013 proprietà di Google. Grazie agli utenti, Waze è in grado di fornire aggiornamenti sul traffico o altri avvenimenti in tempo reale. Per maggiori dettagli sulle funzionalità dell'app consulta https://www.waze.comDal punto di vista forense l'app conserva una buona quantità di dati che possono essere utilizzati come prova.

App Store: https://apps.apple.com/it/app/waze-gps-e-traffico-live/id323229106

Percorsi

Di seguito lo stralcio di Waze del poster della SANS “iOS Third-Party Apps Forensics Reference Guide Poster” con le informazioni più rilevanti per l’analisi dell’app.


Analisi degli artefatti

Per lo studio di questa app ho utilizzato una delle immagini pubbliche di iOS note nel settore e in particolare quella di Josh Hickman iOS 15.3.1 disponibile su DigitalCorporaLa versione di Waze è 4.94.0.3.

Tutti i valori data e ora (timestamp) sono riferiti al tempo UTC e nel formato Unix Epoch che conta il numero di secondi dal 1 gennaio del 1970. Le coordinate sono registrate come valori interi longitudine,latitudinePer esempio la coordinata -78828778,35658493 rappresenta:

  • -78828778 / 100000 = -78.828778 (longitudine)
  • 35658493 / 100000 = 35.658493 (latitudine)

La coordinata finale nel formato latitudine,longitudine è 35.658493,-78.828778.

Alcuni dati mostrati di seguito non contengono i valori originali presenti nei file dell'immagine, sono stati modificati con valori fittizi per una questione di privacy. 

user è un file di testo che contiene le informazioni dell'utente come username, nome, cognome, data e ora del primo avvio, ultima versione aggiornata etc. Le righe sono scritte nel formato "categoria.campo: valore"  senza doppi apici (es: General.First use: 1684547054).

Identificazione dell'account:

  • Realtime.Nickname: dj (nickname);
  • Realtime.Nameusa_df02qthz (username);
  • Realtime.FirstNameDjango (nome);
  • Realtime.LastNameFaiola (cognome);
  • Realtime.PersistentId1|global|1780009754 (identificatore);
  • General.First use1684547054 (primo utilizzo: 20 maggio 2023 01:44:14).

session è un file di testo dove le righe hanno lo stesso formato appena descritto. Questo file contiene le informazioni relative all'ultima sessione attiva: data e ora dell'ultima sincronizzazione, coordinate dell'ultima posizione e le coordinate e indirizzo dell'ultima destinazione.

  • Config.Last synced1684603053991 (ultima sincronizzazione in millisecondi: 20 maggio 2023 17:17:33);
  • GPS.Position-78828778,35658493 (ultima posizione: latitudine=35.658493, longitudine=-78.828778);
  • Navigation.Last position-78829105,35657631 (ultima posizione di navigazione: latitudine=35.657631, longitudine=-78.829105);
  • Navigation.Last dest nameJones Park (destinazione);
  • Navigation.Last dest stateNorth Carolina (regione/stato);
  • Navigation.Last dest citySchool Days Ln (città);
  • Navigation.Last dest streetSchool Days Ln (via);
  • Navigation.Last dest number405 (numero civico).

user.db è il database principale che contiene le tabelle con i luoghi ricercati, preferiti, condivisi e recenti.

Di seguito un dettaglio non esaustivo delle tabelle di interesse:

PLACES (ricercati)

  • id: chiave primaria;
  • name: nome;
  • street: via;
  • city: città;
  • state: regione/stato;
  • country: codice paese;
  • house: numero civico;
  • longitude: longitudine;
  • latitude: latitudine;
  • created_time: data e ora di creazione.

RECENTS (recenti)
  • id: chiave primaria;
  • place_id: chiave esterna del luogo (id della tabella PLACES);
  • name: nome del luogo;
  • created_time: data e ora di creazione;
  • access_time: data e ora dell'ultimo accesso.

FAVORITES (preferiti)

  • id: chiave primaria;
  • place_id: chiave esterna del luogo (id della tabella PLACES);
  • name: nome del luogo;
  • created_time: data e ora di creazione;
  • modified_time: data e ora dell'ultima modifica;
  • access_time: data e ora dell'ultimo accesso.

SHARED_PLACES (condivisi)

  • id: chiave primaria;
  • place_id: chiave esterna del luogo (id della tabella PLACES);
  • name: nome del luogo;
  • created_time: data e ora di creazione;
  • access_time: data e ora dell'ultimo accesso;
  • share_time: data e ora di condivisione;
  • modified_time: data e ora dell'ultima modifica.

spdlog.*logdata

Questi file sono presenti in Internal App Path "/Documents/" e contengono un enorme quantità di informazioni, utili? dipende. Nella versione 4.75.0.0 di Waze questi log non sono presenti.

Dalla prima riga del log

[13:17:22.381 (none) (0x0000000) 592256 2614 warning] *** 4.94.0.3 (-) Starting at 20/05/2023 13:17 ***

è possibile estrapolare sia la versione dell'app "4.94.0.3" che la data e ora di inizio "20/05/2023 13:17" del log stesso.

Ispezionando questi log alla ricerca di potenziali tracce sulle posizioni in tempo reale, la mia attenzione è stata catturata dall'evento GPS_QUALITY della funzione (track_gps_quality).

[13:19:34.181 (usa_df02qthz) (0x0000000) 592256 2614 warning] STAT(buffer#1) GPS_QUALITY {LAT=35656548}{LON=-78834964}{SAMPLE_COUNT=121}{BAD_SAMPLE_COUNT=0}{ACC_AVG=8}{ACC_MIN=6}{ACC_MAX=32}{PROVIDER=GPS}{TIMESTAMP_MS=1684603174181}{TIMESTAMP=1684603174}

[location.cc:459 (track_gps_quality)]

L'orario del TIMESTAMP_MS=1684603174181 (20 maggio 2023 17:19:34.181) è diverso da quello della riga 13:19:34.181, c'è un offset di -4 ore rispetto al tempo UTC, quindi i log sono scritti in ora locale.

Questa "STAT(buffer#1) GPS_QUALITY" è una diagnostica della qualità della posizione GPS e ha una frequenza di circa 2 minuti. Su questi campioni {SAMPLE_COUNT=121} è calcolata l'accuratezza media della posizione {ACC_AVG=8} con un intervallo di {ACC_MIN=6} - {ACC_MAX=32} e dati provenienti dal fornitore {PROVIDER=GPS}. Inoltre ho una latitudine {LAT=35656548} e una longitudine {LON=-78834964} al tempo UTC in millisecondi {TIMESTAMP_MS=1684603174181}.

Ora la domanda è, quando inizia questo tracciamento? Sempre da un'ispezione visiva dei log è possibile verificare che questo evento è conseguenziale a quello del NAVIGATE, ovvero poco dopo l'avvio della navigazione. 

Dalla documentazione dell'immagine iOS 15 di Josh Hickman si può vedere che sono state impostate tre navigazioni:

  1. Holly Springs: inizio alle 11:43 e fine alle 11:51
  2. Wallgreens: inizio alle 13:04  e fine alle 13:11
  3. Jones Park: inizio alle 13:18 e fine alle 13:22

spdlog.3.logdata (Holly Springs)

riga 875 (inizio navigazione): [11:42:04.504 (usa_df02qthz) (0x0000000) 572598 2484 warning] STAT(buffer#1) NAVIGATE {SOURCE=MAP}{TIMESTAMP_MS=1684597324504}{TIMESTAMP=1684597324}

riga 1164 (il primo GPS_QUALITY): [11:43:07.178 (usa_df02qthz) (0x0000000) 572598 2484 warning] STAT(buffer#1) GPS_QUALITY {LAT=35665692}{LON=-78835049}{SAMPLE_COUNT=120}{BAD_SAMPLE_COUNT=0}{ACC_AVG=30}{ACC_MIN=12}{ACC_MAX=48}{PROVIDER=GPS}{TIMESTAMP_MS=1684597387178}{TIMESTAMP=1684597387}

spdlog.2.logdata (Wallgreens)

riga 7049 (inizio navigazione): [13:04:02.991 (usa_df02qthz) (0x0000000) 586193 2557 warning] STAT(buffer#1) NAVIGATE {SOURCE=MAP}{TIMESTAMP_MS=1684602242991}{TIMESTAMP=1684602242}

riga 7168 (GPS_QUALITY): [13:05:15.182 (usa_df02qthz) (0x0000000) 586193 2557 warning] STAT(buffer#1) GPS_QUALITY {LAT=35660814}{LON=-78869167}{SAMPLE_COUNT=121}{BAD_SAMPLE_COUNT=0}{ACC_AVG=9}{ACC_MIN=6}{ACC_MAX=12}{PROVIDER=GPS}{TIMESTAMP_MS=1684602315182}{TIMESTAMP=1684602315}

spdlog.1.logdata (Jones Park)

riga 7101 (inizio navigazione): [13:18:19.683 (usa_df02qthz) (0x0000000) 592256 2614 warning] STAT(buffer#1) NAVIGATE {SOURCE=MAP}{TIMESTAMP_MS=1684603099683}{TIMESTAMP=1684603099}

riga 7353 (GPS_QUALITY): [13:19:34.181 (usa_df02qthz) (0x0000000) 592256 2614 warning] STAT(buffer#1) GPS_QUALITY {LAT=35656548}{LON=-78834964}{SAMPLE_COUNT=121}{BAD_SAMPLE_COUNT=0}{ACC_AVG=8}{ACC_MIN=6}{ACC_MAX=32}{PROVIDER=GPS}{TIMESTAMP_MS=1684603174181}{TIMESTAMP=1684603174}

Queste posizioni possono rappresentare un potenziale tracciato della navigazione. Utilizzando i dati presenti nel database tts.db (Text-To-Speech) che contiene le indicazioni stradali delle navigazioni è possibile ricostruire un percorso più dettagliato cercando di colmare le lacune dei 2 minuti.

tts.db (Text-To-Speech)

Questo database tts.db si trova in Internal App Path "/Library/Caches/tts/" e contiene la trascrizione dell'assistente vocale durante la navigazione con i relativi timestamp. Per ogni voce esiste una tabella con il relativo nome (es: Jane, Ben, Sarah, etc.).

  • text: testo dell'assistente vocale;
  • data: blob con audio; 
  • path: percorso locale (device) del file audio;
  • storage_type: 0=none, 1=blob, 2=file, 3=blob e file;
  • text_type: 0=default, 1=in viaggio?, 2=partenza?, 3=arrivo?;
  • update_time: data e ora dell'aggiornamento.

Plugin per iLEAPP

Al fine di facilitare l'analisi di Waze per iOS ho sviluppato un plugin per iLEAPP (iOS Logs, Events, And Plists Parser) di Alexis Brignoni. Alla data di pubblicazione il plugin è in “pull request” e comunque disponibile sul mio GitHub.

Di seguito alcuni screenshot del report di Waze:


Read More