Introduzione
Lo scopo di questo post è quello di mettere in risalto le differenze tra le vecchie versioni e le attuali, nonché aggiornare le query precedenti. Diversi passaggi sono dati per scontato, quindi prima di iniziare la lettura di questo approfondimento, consiglio di dare un'occhiata se non l'hai già fatto ai precedenti articoli: iOS Burner e iOS Burner - Cache.db.
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 ForensicsReference Guide Poster” con le informazioni più rilevanti per l’analisi dell’app.
Analisi degli artefatti
Per lo studio di questa app ho utilizzato tutte le immagini pubbliche di Josh Hickman disponibili su DigitalCorpora: la versione iOS 13.3.1, 13.4.1, 14.3, 15.3.1, 16.1.2 e 17.3 con le relative versioni di Burner 4.0.18, 4.0.18, 4.3.3, 5.3.8, 5.4.11 e 5.4.11.
Purtroppo alla data odierna l'ultima versione di Burner è la 5.13.1 dell'11 settembre 2024 mentre la 5.4.11 è del 9 gennaio 2024; oltre una quindicina di versioni sono state rilasciate per risolvere bug e per migliorare le prestazioni, ma alcune hanno introdotto anche delle novità come la possibilità di condividere video ad alta qualità. Quindi non ho modo di verificare la correttezza del procedimento di analisi di seguito esposto per queste versioni e/o vedere cosa è cambiato.
Phoenix.sqlite
Per quanto riguarda l'analisi del database SQLite le vecchie query diciamo che hanno qualche problema. Infatti dalle prime versioni in esame fino alla recente 5.4.11 le tabelle hanno subito una ristrutturazione durante questa evoluzione e non solo: sono rimasti campi inutilizzati (vuoti), successivamente rimossi, inseriti nuovi identificatori e per completare l'opera alcune relazioni tra le tabelle sono cambiate.
Accounts
Fino alla versione di Burner 4.3.3 inclusa, la tabella ZBURNER è in relazione con la tabella ZUSER (account) per mezzo della chiave esterna ZBURNER.ZUSER-> ZUSER.Z_PK. Nella versione 5.3.8 il campo ZBURNER.ZUSER è vuoto e nella 5.4.11 è stato rimosso. In iLEAPP la query è dinamica ed è basata sulla presenza o meno del campo ZUSER.
La versione aggiornata "diciamo adattata":
Contatti
Non ci sono modifiche, la query è la stessa.
Numeri Burner
Stesso discorso dell'Account, non avendo più il riferimento tra le tabelle ZBURNER e ZUSER la query è adattata sempre sulla base della presenza del campo ZBURNER.ZUSER.
La versione aggiornata:
SELECTB.Z_PK AS "B_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",B.ZBURNERID AS "burnerId"FROM ZBURNER AS "B"
Messaggi
Per i messaggi la questione si fa un po' più spinosa: diverse sono le colonne (campi) che entrano in gioco. Le relazioni principali sono sempre la ZMESSAGE.ZMESSAGETHREAD->ZMESSAGETHREAD.Z_PK e la ZMESSAGETHREAD.ZCONTACT->ZCONTACT.Z_PK.
Il problema sorge quando i valori ZMESSAGE.ZMESSAGETHREAD e/o ZMESSAGETHREAD.ZCONTACT sono nulli: viene a mancare il collegamento tra il messaggio e il thread e di conseguenza quello tra il thread e il contatto. Come risultato si ha solo il numero di telefono del contatto e non il nome completo.
Come mettere di nuovo in relazione queste tabelle per ottenere i nomi dei contatti?
Nella versione 5.4.11 è presente un nuovo campo sia in ZMESSAGETHREAD.ZCONVERSATIONID che in ZMESSAGE.ZCONVERSATIONID, l'identificatore della conversazione (in pratica il numero di telefono con il prefisso internazionale), utile per creare il nuovo legame. Ma il numero di telefono del contatto, ovvero il contatto, può essere associato a diversi burner quindi bisogna impostare un'ulteriore restrizione su questo nuovo legame tenendo conto dell'identificatore del burner.
Con l'aggiunta della seguente espressione è possibile ristabilire il legame tra il thread e il messaggio.
OR (M.ZMESSAGETHREAD IS NULL AND M.ZBURNERID = MT.ZBURNERID AND M.ZCONVERSATIONID = MT.ZCONVERSATIONID)
Nella versione 5.3.8, ZCONVERSATIONID non esiste, quindi il legame si ottiene con ZCONTACTID o ZCONTACTPHONENUMBER.
OR (M.ZMESSAGETHREAD IS NULL AND M.ZBURNERID = MT.ZBURNERID AND coalesce(M.ZCONTACTID, M.ZCONTACTPHONENUMBER) = MT.ZCONTACTPHONENUMBER)
Per le versioni precedenti alla 4.3.3 inclusa i campi non sono nulli quindi non viene aggiunta nessuna espressione.
Ora bisogna ripristinare il legame dei contatti tra le tabelle ZMESSAGETHREAD e ZCONTACT. Se è presente il campo ZMESSAGE->ZCONTACTID (per la versioni 5.3.8 e 5.4.11) e tenuto conto che nella versione 5.3.8 i valori di ZCONTACTID sono nulli mentre nella 5.4.11 invece sono i valori di ZCONTACTPHONENUMBER ad essere nulli, allora l'espressione finale è:
OR (MT.ZCONTACT IS NULL AND coalesce(M.ZCONTACTID, M.ZCONTACTPHONENUMBER) = C.ZPHONENUMBER)
altrimenti non si aggiunge nulla.
La versione aggiornata:
SELECTMT.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",CASEWHEN (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.ZBODYEND AS "message",CASEWHEN (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 (M.ZASSETURL IS NULL) THEN "Text"WHEN (M.ZTYPE = 2) THEN "Picture"ELSE M.ZTYPEEND 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 coalesce(M.ZCONTACTID, M.ZCONTACTPHONENUMBER) = MT.ZCONTACTPHONENUMBER)LEFT JOIN ZCONTACT AS "C" ON (MT.ZCONTACT = C.Z_PK) OR (MT.ZCONTACT IS NULL AND coalesce(M.ZCONTACTID, M.ZCONTACTPHONENUMBER) = C.ZPHONENUMBER)
Cache.db
Con il rilascio delle nuove immagini per iOS 16 e 17 da parte di Josh Hickman ho eseguito iLEAPP per verificare se i miei plugin svolgono il loro compito. Purtroppo non è stato così. La vecchia query basata solo sulle interrogazioni relative ai messaggi non produce alcun risultato significativo, quindi ho deciso di affrontare la questione.
Le analisi che seguono fanno riferimento all'iOS 17.3 e versione di Burner 5.4.11. Ispezionando la cache ho visto che sono presenti diversi file JSON, qualche immagine e anche la voicemail; quindi ho analizzato i singoli JSON e ho trovato particolarmente interessante alcuni file relativi alle conversazioni e non ai singoli messaggi. Ripercorrendo il percorso all'indietro sono risalito nel database a due tipi di richiesta:
https://phoenix.burnerapp.com/v2/user/<user_id>/burners/<burner_id>/conversations/<phone_number>/messages?pageSize=50&page=1
https://phoenix.burnerapp.com/v2/user/<user_id>/burners/<burner_id>/conversations/<phone_number>/messages/conversations/<phone_number>/messages
La nuova query:
SELECTcr.request_key AS "URL",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 REGEXP "https:\/\/phoenix\.burnerapp\.com(\/v\d)?\/user\/[a-fA-F\d-]{36}(\/messages($|\?.*contactPhoneNumber=.+)|(\/burners\/[a-fA-F\d-]{36}\/conversations\/.+\/messages($|\?.*pageSize=.+)))"
Il file JSON (isDataOnFS=1) 21B2F296-61C6-48FB-AA53-55D632FE4757 nell'Internal App Path "/Library/Caches/com.adhoclabs.burner/fsCachedData/" per esempio contiene le conversazioni.
Per la visualizzazione ho usato dfDataViewer, il mio visualizzatore di (B)Plist, JSON(B), (B)XML, ASN.1, Protobuf, LevelDB, etc. ancora in fase di sviluppo e non pubblico.
21B2F296-61C6-48FB-AA53-55D632FE4757
La struttura tipica di una chiamata vocale: (per esempio l'oggetto [1])
- [1].dateCreated: 1720381116752 (data di creazione nel formato Unix Epoch in millisecondi, 07 luglio 2024 19:38:36);
- [1].direction: Outbound (direzione della conversazione, Outbound=uscita o Inbound=entrata);
- [1].read: true (flag che indica se il messaggio è stato letto, true=letto, false=non letto);
- [1].conversation
- conversationId: +19192059xxx (numero di telefono della conversazione);
- [1].text: null (testo del messaggio);
- [1].messageType: Voice (tipo di messaggio, Text=testo/immagine o Voice=chiamata/voicemail);
- [1].state: CallCompleted (stato della conversazione, CallCompleted=fine chiamata, Voicemail=segreteria telefonica, Delivered=consegnato, CallMissed=chiamata persa);
- [1].callDetails
- durationMinutes: 3 (durata della chiamata in minuti);
- [1].burnerId: 4f6141ae-48dc-408a-8c95-18f830aaaxxx (identificatore del burner);
- [1].userId: 21d2ca9c-aba1-4ada-b7f5-23b0d97acxxx (identificatore univoco dell'utente);
- [1].id: 32361dba-1733-4606-8df3-c8b4289a2xxx (identificatore univoco della conversazione).
La struttura tipica di un messaggio vocale: (per esempio l'oggetto [4])
- [4]. ...
- [4].messageType: Voice (chiamata vocale);
- [4].state: Voicemail (segreteria telefonica);
- [4].voicemail
- audioUrl: https://s3.amazonaws.com/burner-voicemail/prod/b245df6b-a316-417f-9ad8-64bf5c7fbdcd.wav (URL della registrazione audio);
- durationSeconds: 3 (durata dell'audio in secondi).
- [7]. ...
- [7].messageType: Text (testo o immagine con testo);
- [7].state: Delivered (consegnato);
- [7].mediaUrl: https://s3.amazonaws.com/burner-mms/prod/5196a5b3-f089-49c9-a25d-5e654d43583d.jpg (URL dell'immagine).
Ho rimesso insieme tutti questi "pezzi" e il risultato è "stupendo", da zero a 34 messaggi.
iLEAPP 💖
Come di consueto, a supporto del progetto iLEAPP (iOS Logs, Events, And Plists Parser) di Alexis Brignoni ho aggiornato i plugin burner.py e burnerCache.py che alla data odierna sono in “pull request” e comunque disponibili sul mio GitHub.
Di seguito due "screenshot" del report di Burner Cache Messages: iOS 15.3.1 e iOS 17.3
Come di consueto, a supporto del progetto iLEAPP (iOS Logs, Events, And Plists Parser) di Alexis Brignoni ho aggiornato i plugin burner.py e burnerCache.py che alla data odierna sono in “pull request” e comunque disponibili sul mio GitHub.
Di seguito due "screenshot" del report di Burner Cache Messages: iOS 15.3.1 e iOS 17.3
0 comments:
Posta un commento