martedì 10 marzo 2026

Published marzo 10, 2026 by Django Faiola with 0 comment

Deep‑Dive Forense in Box per iOS

Indice dei contenuti

Introduzione

Box per iOS è un'applicazione gratuita sviluppata da Box, Inc., azienda statunitense specializzata in soluzioni di cloud content management e collaborazione aziendale. L’app consente di accedere, archiviare, condividere e gestire file direttamente da dispositivi iPhone e iPad, integrandosi con numerosi servizi di produttività e garantendo elevati standard di sicurezza e protezione dei dati.

Per una panoramica completa delle funzionalità dell’applicazione:
https://www.box.com

App Store:
https://apps.apple.com/it/app/box-the-power-of-content-ai/id290853822

Per lo studio di questa applicazione è stata adottata una metodologia comparativa, esaminando i dati provenienti da due scenari differenti: un'estrazione fisica di un iPhone 8 (iOS 16.7.12) con Box v5.34.1 e un backup iTunes cifrato di un iPhone SE (2nd gen) (iOS 18.5) con installata la versione più recente di Box (v5.54.0).

Percorsi

Per semplicità, nel documento verranno utilizzate le seguenti abbreviazioni:

<ADC>=/private/var/mobile/Containers/Data/Application/<UUID>/
<AGC>=/private/var/mobile/Containers/Shared/AppGroup/<UUID>/

Strutture di interesse forense:

Path File name File type
<AGC>/Library/Preferences/ group.net.box.BoxNet.plist Plist
<AGC>/Documents/db/ Item.db SQLite
<AGC>/File Provider Storage/boxpreview/<userID>/db/ PreviewItem.db SQLite
<AGC>/Documents/offlinefilesinfo/ itemIDs.plist Plist
<AGC>/Documents/offlinefilesinfo/ lastDownloadDates.plist Plist
<AGC>/File Provider Storage/boxpreview/<userID>/cache/files/<fileID>/ * Various

Profilo utente

La configurazione dell’account è memorizzata nel file di preferenze group.net.box.BoxNet.plist, situato nel percorso <AGC>/Library/Preferences/. All’interno del Plist è presente il dizionario lastUserJSON che contiene i metadati relativi all’ultimo utente autenticato sull’applicazione.

Per la visualizzazione del file è stato utilizzato dfDataViewer, il mio strumento (ancora in fase di sviluppo) per l'ispezione di formati strutturati quali Plist/BPlist, JSON/JSONB, XML/BXML, ASN.1, Protobuf e LevelDB.

Le proprietà più rilevanti presenti in lastUserJSON sono:

  • created_at2024-09-29T05:55:27-07:00 - data di creazione dell’account in ISO 8601 con fuso orario (29 settembre 2024 12:55:27).
  • logindj***@***.it - indirizzo email dell’utente.
  • namedj***@***.it - nome visualizzato.
  • has_custom_avatarFalse - indica se l’utente ha impostato un avatar personalizzato.
  • enterprisenull - tipo di account (null per account gratuito; { id: "11446498", type: "enterprise", name: "Acme Inc." }).
  • id36844*** - identificatore univoco dell’utente.
  • timezoneAmerica/Los_Angeles - fuso orario dell’account.
  • languageit - codice della lingua (ISO 639‑1).
  • max_upload_size262144000 - dimensione massima dei file caricabili in byte (262.14 MB).
  • space_amount10737418240 - quota totale in byte (10.74 GB).
  • space_used4268002649 - spazio usato in byte (4.27 GB).

Tutti i file

Il database SQLite Item.db, situato nel percorso <AGC>/Documents/db/, rappresenta l'archivio principale attraverso cui l’app Box gestisce la totalità degli elementi presenti nel cloud dell’utente. Al suo interno sono conservati i riferimenti a file e cartelle, la struttura logica che li collega e l’insieme dei metadati necessari alla sincronizzazione e alla rappresentazione dei contenuti.

L’interfaccia dell’applicazione mostra i file in una struttura ad albero, ma il database adotta un modello di archiviazione completamente flat. Ogni elemento è descritto da un record autonomo nella tabella items, mentre la gerarchia viene ricostruita tramite campi relazionali come parentID.

La figura seguente mostra la rappresentazione del campo jsonData relativo all’elemento Django.mp4, utile per comprendere la struttura interna con cui Box serializza i metadati dei singoli file.

I campi chiave di interesse sono:

  • modelID1659897439061 - identificatore univoco dell’elemento (chiave primaria).
  • type: file - tipo di elemento (file o folder).
  • name: Django.mp4 - nome del file o della cartella.
  • parentID: 286956667057 - identificatore della cartella genitore, modelID.
  • lastNetworkFetchedTimestamp: 1767015862.94753 - data e ora dell’ultima sincronizzazione con il server in Unix Epoch (29 dicembre 2025 13:44:22).
  • jsonData: struttura complessa in formato JSON utilizzata per incapsulare i metadati estesi.

All’interno di jsonData sono presenti ulteriori informazioni rilevanti quali:

  • created_at: 2024-09-29T22:05:44-07:00 - data e ora di creazione su Box in ISO 8601 con fuso orario (30 settembre 2024 05:05:44).
  • modified_at: 2024-09-29T22:05:44-07:00 - data e ora dell’ultima modifica su Box in ISO 8601 con fuso orario (30 settembre 2024 05:05:44).
  • content_created_at: 2024-06-14T01:34:17-07:00 - data e ora di creazione del documento in ISO 8601 con fuso orario (14 giugno 2024 08:34:17).
  • content_modified_at: 2024-06-14T01:34:17-07:00 - data e ora dell’ultima modifica del documento ISO 8601 con fuso orario (14 giugno 2024 08:34:17).
  • file_version.id: 1826012251061 - identificativo univoco della specifica revisione.
  • extension: mp4 - estensione del file.
  • size: 4913127 - dimensione del file in byte (4.7 MB).
  • owned_by: proprietario del file.
    • id: 36844*** - identificatore univoco del proprietario del documento.
    • login: dj***@***.it - indirizzo email dell’utente.
    • name: dj***@***.it - nome visualizzato.
  • modified_by: ultimo utente a modificare il file.
    • id: 36844*** - identificatore univoco dell'ultimo utente che ha modificato il documento.
    • login: dj***@***.it - indirizzo email dell’utente.
    • name: dj***@***.it - nome visualizzato.
  • permissions: { can_preview: 1, can_annotate: 0, can_download: 1, can_delete: 1, can_rename: 1, ..., can_share: 1, can_comment: 1} - permessi assegnati al file.
  • collections: {} - raccolte associate.
  • sha1: 33385923bb60acbc7d0bb8db96432543c2084ae7 - hash SHA1 del file.

La query seguente utilizza una CTE (Common Table Expression) ricorsiva per ricostruire l’intero albero delle directory e generare, per ogni elemento, il percorso assoluto nello spazio virtuale del cloud. Una volta ottenuti i path, la stessa query estrae dal campo jsonData tutti i metadati rilevanti, incluse estensioni, dimensioni, versioni, permessi, informazioni sul proprietario e sulle raccolte.

WITH
  RECURSIVE cte_paths(id, name, path) AS (
  SELECT
    modelID AS id,
    name,
    '/' || name AS path
  FROM items
  WHERE COALESCE(parentID, 0) = 0
  UNION ALL
  SELECT
    i.modelID AS id,
    i.name,
    p.path || '/' || i.name AS path
  FROM items AS i
  JOIN cte_paths AS p ON (i.parentID = p.id)
)

SELECT
  p.id AS "Item ID",
  i.type AS "Type",
  p.name AS "File/Folder Name",
  json_extract(i.jsonData, '$.extension') AS "Extension",
  p.path AS "Full Path",
  json_extract(i.jsonData, '$.shared_link.url') AS "Shared URL",
  json_extract(i.jsonData, '$.description') AS "Description",
  json_extract(i.jsonData, '$.size') AS "File/Folder Size",
  json_extract(i.jsonData, '$.file_version.id') AS "Version ID",
  json_extract(i.jsonData, '$.content_created_at') AS "Content Created At",
  json_extract(i.jsonData, '$.content_modified_at') AS "Content Modified At",
  json_extract(i.jsonData, '$.created_at') AS "Created At",
  json_extract(i.jsonData, '$.modified_at') AS "Modified At",
  i.lastNetworkFetchedTimestamp AS "Last Fetched At",
  CASE
    WHEN EXISTS (
      SELECT 1
      FROM json_each(i.jsonData, '$.collections')
      WHERE json_extract(json_each.value, '$.collection_type') = 'favorites'
    )
    THEN 'Yes'
    ELSE 'No'
  END AS "Favorites",
  (
    SELECT group_concat(json_extract(cn.value, '$.name'), ', ')
    FROM json_each(json_extract(i.jsonData, '$.collections')) AS cn
  ) AS "Collection Names",
  json_extract(i.jsonData, '$.owned_by.login') AS "Owner Login",
  json_extract(i.jsonData, '$.owned_by.name') AS "Owner Name",
  json_extract(i.jsonData, '$.owned_by.id') AS "Owner ID",
  json_extract(i.jsonData, '$.created_by.login') AS "Creator Login",
  json_extract(i.jsonData, '$.created_by.name') AS "Creator Name",
  json_extract(i.jsonData, '$.created_by.id') AS "Creator ID",
  json_extract(i.jsonData, '$.modified_by.login') AS "Modifier Login",
  json_extract(i.jsonData, '$.modified_by.name') AS "Modifier Name",
  json_extract(i.jsonData, '$.modified_by.id') AS "Modifier ID",
  i.SHA1,
  (
    SELECT group_concat(p.key || '=' || p.value, ', ')
    FROM json_each(json_extract(i.jsonData, '$.permissions')) AS p
  ) AS "Permissions"
FROM cte_paths AS p
LEFT JOIN items AS i ON (i.modelID = p.id)
ORDER BY i.type DESC, p.path ASC;

L’immagine seguente mostra il result set generato dalla query, con i percorsi completi ricostruiti tramite la CTE ricorsiva e i metadati estratti dal campo jsonData per ciascun elemento.

La schermata mostra i metadati del file Django.mp4 così come visualizzati nella sezione "Tutti i file" dell’area "Sfoglia" dell’app Box per iOS.

Elementi marcati offline

Gli elementi marcati come offline sono i file che l’utente ha scelto di rendere disponibili localmente sul dispositivo, così da poterli consultare anche in assenza di connettività. L’app Box per iOS non utilizza un flag esplicito nel database Item.db per indicare quali elementi siano stati resi disponibili offline. Un’analisi completa dei file potenzialmente coinvolti nella gestione di questo stato ha permesso di individuare due Plist all’interno dell’App Group Container, che risultano essere gli unici artefatti in grado di ricondurre ai file marcati come offline. I file si trovano nei seguenti percorsi:

  • <AGC>/Documents/offlinefilesinfo/itemIDs.plist
  • <AGC>/Documents/offlinefilesinfo/lastDownloadDates.plist

La figura mostra la struttura dei metadati salvati nei file itemIDs.plist e lastDownloadDates.plist, utilizzati dall’applicazione per tracciare gli elementi marcati come disponibili offline.

itemIDs.plist (a sinistra) contiene una lista di identificatori modelID che corrispondono agli elementi della tabella items marcati dall’utente come disponibili offline.

lastDownloadDates.plist (a destra) è invece strutturato come un dizionario in cui la chiave è il modelID, mentre il valore associato rappresenta il timestamp (data e ora) dell’ultimo download dell’elemento corrispondente.

L'analisi cross-artifact permette di correlare il modelID 1659897439061 presente nei Plist con l'elemento Django.mp4 identificato precedentemente in Item.db, confermandone lo stato di persistenza locale:

  • itemIDs.plist[0]: 1659897439061 - identificatore dell’elemento marcato offline.
  • lastDownloadDates.plist[1659897439061]: 30/09/2024 05:05:44 - data e ora (UTC) dell’ultimo download.

La schermata mostra la sezione "Offline" dell’area "Sfoglia", con l’elenco degli elementi che l’utente ha reso disponibili localmente sul dispositivo.

Questi artefatti sono esclusi dai backup standard di iOS e possono essere acquisiti esclusivamente tramite estrazioni di tipo Full File System.

Raccolte e preferiti

L'area "Raccolte" introduce un layer di organizzazione logica basato su tag, ortogonale alla gerarchia delle directory. Questa struttura riflette un'intenzionalità dell'utente che trascende la posizione fisica dei file, fornendo preziosi indizi sulle priorità operative del soggetto analizzato.

A differenza dei metadati strutturali, queste informazioni non risiedono in colonne dedicate della tabella items. L’app Box per iOS utilizza la lista collections, incapsulata all’interno del campo jsonData, per descrivere l’appartenenza di un elemento a uno o più raggruppamenti.

Le tipologie principali sono due:

  • "Le mie raccolte": raggruppamenti personalizzati creati dall'utente, identificati dalla chiave collection_type con valore personal.
  • "Preferiti": risorse contrassegnate come rilevanti, identificate dalla chiave collection_type con valore favorites.

La struttura di un record di collections marcato come preferito è la seguente:

  • collection_type: favorites - tipo di raccolta (Preferiti).
  • id: 24319741806 - identificatore univoco della raccolta.
  • name: Favorites - etichetta visualizzata nell'interfaccia.

Nella query utilizzata per l'estrazione di "Tutti i file" è già integrata una logica che separa le raccolte: viene generato un elenco delle etichette associate e una colonna Favorites valorizzata a Yes quando l’elemento risulta aggiunto ai Preferiti.

Commenti e annotazioni

L’analisi delle interazioni collaborative si è focalizzata sul database SQLite annotations.db,  memorizzato nel percorso <AGC>/Documents/db/.

Questo archivio gestisce due categorie principali di artefatti:

  • commenti, rappresentati come contenuti testuali associati a un file;
  • annotazioni, che includono evidenziazioni, marcature grafiche e note applicate ai documenti.

Ogni record della tabella comments rappresenta un’interazione associata a una specifica risorsa e la figura che segue mostra la rappresentazione del campo jsonData con i metadati del commento.

I campi di maggiore interesse includono:

  • id692189957 - identificatore del commento (chiave primaria).
  • fileID1659918589799 - identificatore del file o della cartella (chiave esterna), corrispondente a modelID della tabella items in Item.db.
  • createdAt1766907068 - data e ora di creazione in Unix Epoch (28 dicembre 2025 07:31:08).
  • networkFetchedAt1766909060.58597 - data e ora dell’ultima sincronizzazione con il server in Unix Epoch (28 dicembre 2025 08:04:20).
  • jsonData: struttura JSON contenente i metadati principali.
    • modified_at: 2025-12-28T07:31:08Z - data e ora dell’ultima modifica in ISO 8601 Zulu (28 dicembre 2025 07:31:08).
    • message: Week 29 - 2025 - This Week In 4n6 - testo del commento.
    • created_by: metadati dell'autore del commento.
      • id36844*** - identificatore univoco.
      • logindj***@***.it - login
      • namedj***@***.it - nome visualizzato.
    • tagged_message: null - stringa contenente le eventuali menzioni; le @mention sono formattate come @[id:username].

Oltre ai commenti testuali, Box gestisce le annotazioni grafiche come entità distinte nella tabella annotations. I campi principali includono:  

  • id28238507280 - identificatore dell'annotazione (chiave primaria).
  • createdAt1766909509 - data e ora di creazione in Unix Epoch (28 dicembre 2025 08:11:49).
  • modifiedAt1766909509 - data e ora dell'ultima modifica in Unix Epoch (28 dicembre 2025 08:11:49).
  • networkFetchedAt1769438782.542 - data e ora dell’ultima sincronizzazione con il server in Unix Epoch (26 gennaio 2026 14:46:22).
  • createdByJSONData: struttura JSON con i metadati dell'autore.
    • id: 36844*** - identificatore univoco dell'autore.
    • login: dj***@***.it - login.
    • name: dj***@***.it - nome visualizzato.
  • modifiedByJSONData: struttura JSON con i metadati dell'ultimo utente a modificare l'annotazione.
    • id36844*** - identificatore univoco dell'utente.
    • logindj***@***.it - login.
    • namedj***@***.it - nome visualizzato.
  • descriptionJSONData: struttura JSON con i metadati dell'annotazione.
    • messagela 0.3.5 è vecchia, la versione corrente è la 0.4.1 - testo dell'annotazione.

Entrambe le tipologie vengono aggregate tramite una tabella di raccordo denominata fileActivity che centralizza le informazioni relative a commenti e annotazioni associate ai singoli file.

I campi del database sono i seguenti:

  • createdAt1766909509 - data e ora di creazione dell'attività in Unix Epoch (28 dicembre 2025 08:11:49).
  • id28238507280 - identificativo univoco dell'attività.
  • typeannotation - tipo di attività (comment o annotation).
  • fileId1659918589799identificatore del file o della cartella corrispondente a modelID della tabella items in Item.db.

La seguente query consente di aggregare in un’unica vista le attività associate ai file, includendo sia commenti sia annotazioni grafiche, sfruttando la tabella di raccordo fileActivity.

SELECT
  datetime(fa.createdAt, 'unixepoch') AS "Created At",
  fa.id,
  fa.type,
  CASE
    WHEN fa.type = 'comment' THEN json_extract(c.jsonData, '$.modified_at')
    WHEN fa.type = 'annotation' THEN datetime(a.modifiedAt, 'unixepoch')
    ELSE NULL
  END AS "Modified At",
  fa.fileID,
  i.name,
  CASE
    WHEN fa.type = 'comment' THEN json_extract(c.jsonData, '$.message')
    WHEN fa.type = 'annotation' THEN json_extract(a.descriptionJSONData, '$.message')
    ELSE NULL
  END AS "Message",
  CASE
    WHEN fa.type = 'comment' THEN json_extract(c.jsonData, '$.tagged_message')
    ELSE ''
  END AS "Tagged Message",
  CASE
    WHEN fa.type = 'comment' THEN c.isReply
    ELSE NULL
  END AS "isReply",
  CASE
    WHEN fa.type = 'comment' THEN json_extract(c.jsonData, '$.created_by.login')
    WHEN fa.type = 'annotation' THEN json_extract(a.createdByJSONData, '$.login')
    ELSE NULL
  END AS "Creator Login",
  CASE
    WHEN fa.type = 'comment' THEN json_extract(c.jsonData, '$.created_by.name')
    WHEN fa.type = 'annotation' THEN json_extract(a.createdByJSONData, '$.name')
    ELSE NULL
  END AS "Creator Name",
  CASE
    WHEN fa.type = 'comment' THEN json_extract(c.jsonData, '$.created_by.id')
    WHEN fa.type = 'annotation' THEN json_extract(a.createdByJSONData, '$.id')
    ELSE NULL
  END AS "Creator ID",
  CASE
    WHEN fa.type = 'comment' THEN json_extract(c.jsonData, '$.modified_by.login')
    WHEN fa.type = 'annotation' THEN json_extract(a.modifiedByJSONData,'$.login')
    ELSE NULL
  END AS "Modifier Login",
  CASE
    WHEN fa.type = 'comment' THEN json_extract(c.jsonData, '$.modified_by.name')
    WHEN fa.type = 'annotation' THEN json_extract(a.modifiedByJSONData, '$.name')
    ELSE NULL
  END AS "Modifier Name",
  CASE
    WHEN fa.type = 'comment' THEN json_extract(c.jsonData, '$.modified_by.id')
    WHEN fa.type = 'annotation' THEN json_extract(a.modifiedByJSONData, '$.id')
    ELSE NULL
  END AS "Modifier ID",
  datetime(fa.networkFetchedAt, 'unixepoch') AS "Fetched At"
FROM fileActivity AS fa
LEFT JOIN comments AS c ON (fa.id = c.id)
LEFT JOIN annotations AS a ON (fa.id = a.id)
LEFT JOIN items AS i ON (fa.fileID = i.modelID)
ORDER BY i.name, fa.createdAt

La query di aggregazione produce un result set unificato che include tutte le attività associate ai file, siano esse commenti o annotazioni grafiche.

L'analisi di annotations.db permette di ricostruire non solo la cronologia delle modifiche, ma il vero e proprio flusso comunicativo tra gli utenti. La capacità di correlare annotazioni grafiche e commenti testuali a uno specifico modelID fornisce un quadro completo della collaborazione su ogni singolo asset digitale.

Lo screenshot dell’interfaccia mostra la sezione "Attività" relativa al file selezionato. I commenti sono evidenziati da un bordo giallo, mentre le annotazioni sono contrassegnate da un bordo rosso.

Anteprime e originali

Box non genera una singola miniatura per ciascun file, ma mantiene più rappresentazioni in formati e dimensioni differenti, ottimizzate per le varie esigenze dell’interfaccia (anteprima rapida, vista elenco, griglia, dettaglio del file, ecc.). La gestione di queste rappresentazioni è affidata al database SQLite PreviewItem.db memorizzato nel percorso <AGC>/File Provider Storage/ boxpreview/<userID>/db/. Questo archivio contiene i metadati relativi alle miniature e alle anteprime incluse le informazioni sulle versioni, sulle dimensioni disponibili e sui riferimenti ai file locali utilizzati dall’app per la visualizzazione.

La figura che segue mostra una tipica rappresentazione del campo representations, nello specifico quella relativa all'elemento Django.mp4.

I campi di interesse sono presenti nella tabella previewItems:

  • fileID1659897439061 - identificatore dell'elemento (chiave esterna), corrispondente a modelID della tabella items in Item.db.
  • name: Django.mp4 - nome del file.
  • dataDictionary: <plist> - i metadati in formato Plist.
  • lastAccessedDate1768715535.43314 - data e ora dell’ultima apertura o visualizzazione in Unix Epoch (18 gennaio 2026 05:52:15).
  • sha133385923bb60acbc7d0bb8db96432543c2084ae7 - hash SHA1 del file.
  • representations: struttura JSON che definisce le diverse varianti dell'asset.
    • properties.dimensions: 320x320 - indica la risoluzione dell’asset generato.
    • properties.paged: false - specifica se l’anteprima si riferisce a un documento multipagina (PDF/Word) o a un file singolo (immagini/video).
    • representation: jpg - indica il tipo di file.
    • content.url_template: https://public.boxcloud.com/api/2.0/internal_files/1659897439061/versions/1826012251061/representations/jpg_320x320/ content/{+asset_path} - definisce l'endpoint remoto per il download della risorsa. La struttura della stringa include il parametro versions/1826012251061; identificatore univoco della versione del documento scaricata e consultata.
La query di seguito mostra come estrarre tutte le rappresentazioni di un file insieme ai relativi metadati, come dimensioni, tipo, versione, data dell’ultimo accesso e hash del file originale.
SELECT
  p.fileID AS "Item ID",
  p.name AS "File Name",
  json_extract(e.value, '$.properties.dimensions') AS "Preview Dimensions",
  LOWER(json_extract(e.value, '$.representation')) AS "Preview Extension",
  TRIM(
    SUBSTR(
      json_extract(e.value, '$.info.url'),
      INSTR(json_extract(e.value, '$.info.url'), 'versions/') + 9,
      INSTR(SUBSTR(json_extract(e.value, '$.info.url'), INSTR(json_extract(e.value, '$.info.url'), 'versions/') + 9), '/') - 1
    )
  ) AS "Version ID",
  datetime(p.lastAccessedDate, 'unixepoch') AS "Last Accessed At",
  p.sha1 AS "Original SHA1"
FROM previewItems AS p
JOIN json_each(p.representations, '$.entries') AS e
ORDER BY p.lastAccessedDate DESC, p.fileID, LENGTH("Preview Dimensions") DESC, "Preview Dimensions" DESC

L'immagine che segue mostra il result set con i dati estratti dalla query sulle rappresentazioni dei file. Si osservano chiaramente diverse dimensioni delle anteprime, il tipo di file (estensione), l'identificativo della versione, la data dell'ultimo accesso e l'hash SHA1 originale associato a ciascun elemento. 

L'archiviazione fisica delle anteprime non risiede nel database ma è organizzata in file distribuiti nei percorsi:
<AGC>/File Provider Storage/boxpreview/<userID>/cache/files/<fileID>/

Come evidenziato dalla struttura del percorso, <userID> identifica l’account dell’utente, mentre <fileID> corrisponde all’identificativo univoco del file, equivalente al valore modelID presente nella tabella items del database Item.db.

All'interno della directory <fileID> si distinguono due tipi di contenuto:

  • File originali: situati nella sottocartella "original/", mantengono nome ed estensione originali.
  • Anteprime e miniature: organizzate in cartelle denominate secondo il formato e la risoluzione "<extension>_<dimensions>/" (es. jpg_1024x1024/).

Le ricostruzioni dei percorsi relativi alla miniatura (riga 25 nei risultati della query) e al file originale del video Django.mp4 sono ottenute a partire dalla directory:
<AGC>/File Provider Storage/boxpreview/36844***/cache/files/1659897439061/

I due riferimenti risultanti sono:

  • jpg_1024x1024/Django.mp4
  • original/Django.mp4

Si osserva che, nel caso della miniatura, il nome del file non presenta l’estensione .jpg, pur essendo un’immagine generata dal sistema. 

L'immagine che segue mostra la rappresentazione della miniatura oggetto dell’analisi generata automaticamente dal sistema e archiviata nella cartella delle anteprime.

Recenti

La ricostruzione delle attività utente nell’app Box per iOS si basa sull’analisi del database SQLite Recents.db, localizzato nell'App Group Container in "/Documents/db/". Questo archivio registra le interazioni effettive dell’utente, tra cui visualizzazioni, modifiche e caricamenti, seguendo la struttura dell’endpoint Recent Items.

Ogni record della tabella recents rappresenta un’interazione e include i seguenti campi di interesse:

  • itemID1659897439061 - identificatore dell’interazione (chiave esterna), corrispondente a modelID della tabella items in Item.db.
  • interactionTimeStamp1766557059 - data e ora dell’ultima apertura o visualizzazione in Unix Epoch (24 dicembre 2025 06:17:39).
  • itemTypefile - tipo di elemento (file o folder).
  • interactionTypeitem_preview - tipo di interazione dell’utente (es. item_preview, item_upload, item_comment e item_open).
  • interaction_shared_linknull - se l’elemento è stato visualizzato tramite un collegamento condiviso contiene il link.
SELECT
  datetime(r.interactionTimeStamp, 'unixepoch') AS "Interaction Timestamp",
  r.itemType,
  i.name AS "File name",
  json_extract(i.jsonData, '$.size') AS "File Size",
  r.interactionType AS "Interaction Type",
  r.sharedLinkURL AS "Shared Link URL",
  r.itemID
FROM recents AS r
LEFT JOIN items AS i ON (r.itemID = i.modelID)

Lo screenshot dell’interfaccia mostra la sezione Recenti dell'area Sfoglia.

iLEAPP 💖

Come di consueto, a supporto del progetto iLEAPP (iOS Logs, Events, And Plists Parser) di Alexis Brignoni, ho sviluppato il plugin box.py. Il modulo automatizza l'estrazione e la correlazione di tutti i database e i plist analizzati in questo articolo, normalizzando i dati in un formato pronto per l'analisi forense.

Il plugin è attualmente in fase di "pull request" e comunque disponibile per il download e il testing sul mio GitHub.

Di seguito, alcuni screenshot dell'output generato dal report di Box:







Read More

martedì 15 luglio 2025

Published luglio 15, 2025 by Django Faiola with 0 comment

Aggiungere un'app con APK distinti a dfAPKdngrader

 

Introduzione

Nel precedente articolo Aggiungere un'app a dfAPKdngrader, è stata descritta la procedura per integrare un'app personalizzata non inclusa nell'elenco predefinito dei package del software. In questo nuovo articolo, invece, viene approfondito il metodo per aggiungere un'app che non incorpora il supporto multi-architettura all'interno del medesimo package, condizione che richiede una gestione separata dei file APK.

Esistono applicazioni, dove il package APK è distribuito per una specifica architettura, ad esempio ARMv7. Per installare l'app su un dispositivo con architettura a 32 bit Intel (x86) o MIPS32, è necessario scaricare l'APK compatibile con tali architetture.

Nel precedente articolo, non l'ho scritto, ma dfAPKdngrader supporta anche questa modalità, in quanto l'identificazione dell'app non è basata principalmente sul nome del package come per molti altri software (ad es. packageName=com.whatsapp).

Su diversi dispositivi con sistema Android 10 o versione successive, è possibile che sia abilitata esclusivamente l'esecuzione di codice a 64 bit; in questi casi il supporto a 32 bit è stato rimosso rendendo impossibile il downgrade dell'app con le vecchie architetture come ARMv7. Di conseguenza, avere entrambi i package è fondamentale, soprattutto per garantire la compatibilità con i dispositivi più moderni. 

Per comprendere meglio quanto è stato detto, di seguito viene mostrato come personalizzare l'app del lettore multimediale Lark Player.

Lark Player

Questa applicazione rappresenta un caso d'uso, in quanto presenta APK distinti per ciascuna architettura supportata. Prima di procedere alla descrizione della configurazione, di seguito sono riportati i due blocchi (oggetti) del file apk.json relativi ai package per l'architettura ARMv7 (ABI armeabi-v7a) e ARMv8-A (ABI arm64-v8a).

file di configurazione apk.json:

{
    "baseName": "com.dywx.larkplayer-v7a",
    "packageName": "com.dywx.larkplayer",
    "marketName": "Lark Player",
    "category": "Music & Audio",
    "versionCode": 22230104,
    "versionName": "2.23.1",
    "minSdk": 17,
    "abiList": "armeabi-v7a",
    "url": "1El6zoZD23k_Fbhaw5LAgIkmN_8aBbS7o",
    "md5": "7c6650d968bbb229d1d9890261ec45e2"
},
{
    "baseName": "com.dywx.larkplayer-v8a",
    "packageName": "com.dywx.larkplayer",
    "marketName": "Lark Player",
    "category": "Music & Audio",
    "versionCode": 22230107,
    "versionName": "2.23.1",
    "minSdk": 17,
    "abiList": "arm64-v8a",
    "url": "1JS1NnHI3G8oKNL4vFn-L8KFGrAlam8Yq",
    "md5": "fb8c2d9235da0cac4515716b1b25f981"
}

La versione dell'app (versionName) è la stessa per entrambe le configurazioni, almeno in questo caso, la 2.23.1. Tuttavia, è possibile definire versioni distinte, limitatamente a una per ciascuna tipologia di architettura.

La chiave baseName rappresenta l'identificatore univoco dei pacchetti. In assenza di un valore esplicito, o se lasciato vuoto, è associato al valore della chiave packageName per l'identificazione dell'applicazione. In tutte le configurazioni presenti nel file apk.json, non è mai stata utilizzata. Tutti gli APK  forniti sono prevalentemente multi-architettura come indicato dai valori definiti nella chiave abiList.

Per discriminare le due architetture della stessa app, sono stati definiti altrettanti identificatori (baseName): com.dywx.larkplayer-v7 per la l'architettura ARMv7 e com.dywx.larkplayer-v8 per l'architettura ARMv8-A. I due identificatori sono generici e devono essere ovviamente univoci. In questa configurazione è stata utilizzata la notazione "packageName-val". Per una Intel a 32 bit, ad esempio, com.dywx.larkplayer-i32 o com.dywx.larkplayer-x86.

Per questa app, nonostante versionName è 2.23.1 per entrambe le configurazioni, il versionCode è diverso: 22230104 e 22230107. Quindi fate molta attenzione a scrivere i valori appropriati.

Uno screenshot di questa nuova configurazione:

Volendo, è possibile assegnare a marketName un nome che consenta di capire a colpo d'occhio a quale versione è associato; ad esempio, Lark Player arm64-v8a o qualsivoglia altra denominazione.

ATTENZIONE: Non è stato ancora verificato, ma può accadere che un'app progettata per versioni di Android precedenti alla 10 o compatibile con istruzioni a 32 bit mostri, tra i pacchetti candidati al downgrade, entrambe le versioni. In tal caso, è consigliabile selezionare quella nativa anziché quella retrocompatibile. 

In occasione di questo piccolo articolo, ho deciso di pubblicare anche la lista delle nuove app supportate.

Lo ripeto volentieri: se avete realizzato dei package di app non ancora presenti nell'elenco, contattatemi e li renderò disponibili a tutti!😄.

Ultima versione

Windows | Linux

Il software è proprietario (Freeware - closed source) e per l'uso commerciale considera una piccola donazione valida come autorizzazione implicita al suo utilizzo.

 

Elenco delle nuove app supportate

  1. NumBuster!
  2. LovePlanet
  3. Swiggy
  4. PayByPhone
  5. Domino's Pizza France
  6. Yemeksepeti
  7. Pull&Bear
  8. MyPertamina
  9. Pluto TV
  10. Bolt Driver (Taxify Driver)
  11. Yubo (Yellow)
  12. WEBTOON
  13. Mrsool
  14. Hollister
  15. Veepee
  16. Orbitz
  17. Trafi
  18. Muzz (muzmatch)
  19. 1km
  20. Shopee
  21. Wego
  22. Fever
  23. OpenTable
  24. Wattpad
  25. Tagged
  26. Cloud (Cloud Mail.Ru)
  27. willhaben
  28. OfferUp
  29. Beyond Menu
  30. Microsoft Outlook
  31. Lark Player
Read More

sabato 3 maggio 2025

Published maggio 03, 2025 by Django Faiola with 0 comment

iOS BeReal - Photos & Friends Daily (Cache.db & EntitiesStore.sqlite)

Indice dei contenuti

Introduzione

Questo secondo articolo è un approfondimento dell'analisi di BeReal focalizzato principalmente sul database SQLite Cache.db presente nell'Internal App Path "/Library/Caches/AlexisBarreyat.BeReal/", la cache.

Durante l'analisi condotta nel precedente articolo iOS BeReal - Photos & Friends Daily, ho postato diversi BeReal mentre ascoltavo la musica con Spotify per ricostruire l'object music del JSON, ma non ho trovato alcun riferimento negli stessi mentre nelle risposte alle richieste API nel database Cache.db sono presenti.

Un nuovo "attore è entrato in scena", il database EntitiesStore.sqlite presente nell'Internal App Group Path "/" del contenitore di gruppo  condiviso "/private/var/mobile/Containers/Shared/AppGroup/<APP_GUID>". In realtà questo database SQLite era già presente da qualche versione, ma non popolato e in continuo aggiornamento. Una nuova banca dati ricca di informazioni: utenti, post, commenti, realmoji etc.

BeReal è un social network francese lanciato nel 2020 da Alexis Berreyat e Kevin Per maggiori dettagli sulle funzionalità dell'app consulta https://bereal.com.

App Store: https://apps.apple.com/us/app/bereal-photos-friends-daily/id1459645446

Per lo studio di questa app sono state utilizzate le versioni 4.0.0 (Dec 18, 2024) e successive fino alla 4.18.0 (Apr 14, 2025) del mio iPhone 8 iOS 16.7.10 test e quelle delle immagine pubbliche di Josh Hickman iOS 17.3, 16.1.2 e 15.3.1 con le rispettive versioni 2.24.0, 1.21.4 e 1.1.0.

Percorsi

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

Cache.db

Una breve introduzione sul database Cache.db prima di iniziare l'analisi serve a chiarire i concetti che seguiranno. 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. Due sono le tabelle principali di interesse:

  • cfurl_cache_response: contiene i dati e l'ora della richiesta;
  • 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 come detto precedentemente, i brani musicali ascoltati durante la pubblicazione del BeReal. Se la dimensione dei dati è di 4096 byte o superiore sono   memorizzati su file (cfurl_cache_response.isDataOnFs=1) con il nome dell'UUID assegnato al campo cfurl_cache_receiver_data.receiver_data, altrimenti sono 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".

Account

L'API di richiesta del profilo: https://mobile-l7.bereal.com/api/person/me

SELECT
    crd.entry_ID,
    cr.request_key AS "URL",
    crd.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:\/\/mobile[-\w]*\.bereal\.com\/api\/person\/me$"

La risposta in JSON:

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.

L'object root:

  • createdAt2024-12-19T15:59:49.277Z (data di creazione nel formato ISO 8601 19 dicembre 2024 15:59:49);
  • type: USER (tipo di profilo USER=regular, REAL_BRAND=marchi e REAL_PERSON=celebrità);
  • fullnameDjango (nome completo);
  • usernamedjango.f (username/nickname);
  • profilePicture: (immagine del profilo)
    • urlhttps://cdn-us1.bereal.network/Photos/LRHP7nD4UhfzEJacyRtuZE37txxx/profile/CBEd9NNTW-igELqojHxxx.webp (URL dell'immagine del profilo);
    • width1000 (larghezza in pixel);
    • height1000 (altezza in pixel);
  • gender: (identità di genere, ad es. MALE, FEMALE, etc.);
  • birthdate197x-08-1xT00:00:00.000Z (data di nascita/compleanno nel formato ISO 8601 1x agosto 197x);
  • biographyDigital Forensics Consultant (biografia);
  • countryCodeIT (codice paese);
  • regioneurope-west (zona);
  • locationFondi (LT) (luogo/indirizzo);
  • phoneNumber+393494955xxx (numero di telefono);
  • devices: (array di dispositivi)
    • [0].timezoneEurope/Rome (fuso orario);
    • [0].clientVersion4.1.1 (versione dell'app);
    • [0].deviceiPhone10,1 16.7.10 (id modello e versione iOS);
    • [0].deviceId6F3A454F-8C8D-408F-85E9-B5427D715xxx (UID del dispositivo);
  • isPrivatefalse (è un profilo privato?);
    • realmojis: (array di emoji creati con i selfie)
      • [0].emoji: 👍 (emoji);
      • [0].media: (informazioni sul media)
        • urlhttps://cdn-us1.bereal.network/Photos/LRHP7nD4UhfzEJacyRtuZE37txxx/realmoji/JVgRDw52ClLUWdDe.webp (URL del RealMoji);
    • idLRHP7nD4UhfzEJacyRtuZE37txxx (identificatore univoco dell'account).

    Persone

    L'API di richiesta delle persone: https://mobile-l7.bereal.com/api/person/profiles/<UID>?withPost=true

    SELECT
        crd.entry_ID,
        cr.request_key AS "URL",
        crd.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:\/\/mobile[-\w]*\.bereal\.com\/api\/person\/profiles\/[\w-]+\?"

    La risposta in JSON del mio amico Luigi:

    L'object root:

    • createdAt2024-12-29T11:17:27.724Z (data di creazione nel formato ISO 8601 29 dicembre 2024 11:17:27);
    • typeUSER (tipo di profilo USER=regular, REAL_BRAND=marchi e REAL_PERSON=celebrità);
    • fullnameLuigi (nome completo);
    • usernamelmsconos (username/nickname);
    • profilePicturenull (URL dell'immagine del profilo);
    • biographynull (biografia);
    • locationnull (luogo/indirizzo);
    • relationship: (dettagli della relazione);
      • status: accepted (richiesta accettata);
      • friendedAt2025-01-01T09:13:46.000Z (amici dal, nel formato ISO 8601 29 dicembre 2024 11:19:42);
      • commonFriends: (amici in comune);
        • total: 0 (numero di amici in comune);
    • links: (array di collegamenti)
      • [?].url: (URL);
      • [?].displayText: (testo visualizzato);
    • streakLength0 (numero di BeReal consecutivi postati quando si riceve la "notifica ⚠️È l'ora di BeReal ⚠️"; F.A.Q.);
    • beRealOfTheDay: (BeReal del giorno)
      • postExistsfalse (ci sono post?);
      • userPosts: (informazioni sui post)
        • posts: (array di post)
      • lastPostDate:  (data dell'ultimo post, nel formato ISO 8601);
      • momentId: (identificatore univoco del momento);
      • user: (dati dell'utente, ad es. id, username, etc.);
    • isPrivatefalse (è un profilo privato?);
    • idmDO7GN3gKaY7d8xTxujhQoY0cxxx (identificatore univoco della persona).

    I miei Amici

    L'API di richiesta delle persone: https://mobile-l7.bereal.com/api/relationships/friends/?page

    SELECT
        crd.entry_ID,
        cr.request_key AS "URL",
        crd.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:\/\/mobile[-\w]*?\.bereal\.com\/api\/relationships\/friends($|\/\?page)"

    La risposta in JSON del mio amico Luigi:

    L'array data:

    • [0].fullnameLuigi (nome completo);
    • [0].usernamelmsconos (username/nickname);
    • [0].profilePicturenull (URL dell'immagine del profilo);
    • [0].statusaccepted (richiesta accettata);
    • [0].idmDO7GN3gKaY7d8xTxujhQoY0cxxx (identificatore univoco).

    Richieste di amicizia

    L'API di richiesta di amicizia inviate e ricevute: https://mobile-l7.bereal.com/api/relationships/friend-requests/sent?page e https://mobile-l7.bereal.com/api/relationships/friend-requests/received?page

    SELECT
        crd.entry_ID,
        cr.request_key AS "URL",
        crd.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:\/\/mobile[-\w]*\.bereal\.com\/api\/relationships\/friend-requests\/(received|sent)\?page"

    La risposta in JSON delle richieste inviate:

    L'array data:

    • [0].updatedAt2024-12-31T18:30:38.417Z (stato aggiornato alla data, nel formato ISO 8601 31 dicembre 2024 18:30:38);
    • [0].fullnameLuigi (nome completo);
    • [0].usernamelmsconos (username/nickname);
    • [0].statussent (richiesta inviata, oppure canceled se annullata);
    • [0].mutualFriends0 (amici in comune);
    • [0].idmDO7GN3gKaY7d8xTxujhQoY0cxxx (identificatore univoco).

    Le richieste di amicizia ricevute hanno la stessa struttura di quelle inviate, si differenziano sostanzialmente per il valore di status.

    • [0].statuspending (in attesa di conferma, oppure rejected se rifiutata);

    Utenti bloccati

    L'API di richiesta della lista degli utenti bloccati: https://mobile-l7.bereal.com/api/moderation/block-users?page

    SELECT
        crd.entry_ID,
        cr.request_key AS "URL",
        crd.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:\/\/mobile[-\w]*\.bereal\.com\/api\/moderation\/block-users\?page"

    La risposta in JSON con la lista degli utenti bloccati:

    L'array data:

    • [0].blockedAt2024-12-29T14:16:03.180Z (data del blocco nel formato ISO 8601 29 dicembre 2024 14:16:03);
    • [0].fullnameLuigi (nome completo);
    • [0].usernamelmsconos (username/nickname);
    • [0].idmDO7GN3gKaY7d8xTxujhQoY0cxxx (identificatore univoco).

    Le tue Memorie

    L'API di richiesta della memorie personali: https://mobile-l7.bereal.com/api/feeds/memories-v2/<UID>

    SELECT
        crd.entry_ID,
        cr.request_key AS "URL",
        crd.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:\/\/mobile[-\w]*\.bereal\.com\/api\/feeds\/memories-v(\d)+\/[\w-]+$"

    La risposta in JSON della memoria entry_ID=35:

    L'array posts (esempio post=0):

    • memoryDay2024-12-21 (data del ricordo nel formato YYYY-MM-DD 21 dicembre 2024);
    • [0].isLatefalse (il BeReal è successivo allo slot temporale di due minuti per postare?);
    • [0].isMain: true (il BeReal è quello principale? true=main, false=bonus);
    • [0].visibilities: (array di stringhe con la visibilità)
      • [0]friends (amici, ad es. friends=amici, friends-of-friends=amici di amici, public=pubblico);
    • [0].primary: (tipicamente la camera posteriore)
      • urlhttps://cdn-us1.bereal.network/Photos/LRHP7nD4UhfzEJacyRtuZE37txxx/post/qRKzdxZ26Kgt1rVi.webp (URL del media);
      • mediaType: image (image=foto o video=video);
        • width1500 (larghezza dell'immagine);
        • height2000 (altezza dell'immagine);
      • [0].secondary: (selfie, stessa struttura del primary);
      • [0].thumbnail: (miniatura della primary, stessa struttura del primary)
        • urlhttps://cdn-us1.bereal.network/cdn-cgi/image/height=130/Photos/LRHP7nD4UhfzEJacyRtuZE37txxx/post/qRKzdxZ26Kgt1rVi.webp (URL della miniatura);
        • width97 (larghezza della miniatura);
        • height130 (altezza della miniatura);
        • mediaTypeimage;
      • [0].takenAt2024-12-21T18:39:46.927Z (data dello scatto nel formato ISO 8601 21 dicembre 2024 18:39:46);
      • [0].captionC.C. Aprilia2 (titolo);
      • [0].location: (coordinate del posto, se attiva la geo-localizzazione)
        • latitude??? (latitudine);
        • longitude??? (longitudine);
      • [0].comments: (array con commenti);
      • [0].realmojis: (array di emoji creati con i selfie);
      • [0].tags: (array di amici/persone taggate);
      • [0].idJqBYXSaKEoBK7G4-3Zxxx (identificatore univoco del BeReal).

      L'identificatore del ricordo/memoria è quello della richiesta API, nello specifico <UID>=RyKjYfWYDaqA4jql2SA9r (Moment ID).

      Nel caso di un BeReal con audio condiviso con Spotify versione Free, la struttura del music è la seguente:

      L'object music:

      • isrcITB007770537 (International Standard Recording Code è il codice identificativo della registrazione);
      • providerspotify (fornitore del servizio, ad es. spotify, apple per Apple Music? da verificare);
      • providerId: 0RbhVoxOZE8BniSgmDbaXV (identificativo della traccia musicale, ovvero il track_id);
      • visibility: public (visibilità pubblica).

      Nel caso di Spotify, seppur non avendo direttamente l'URL come nelle vecchie versioni di BeReal, ove è presente la chiave openUrl, è possibile riscostruire l'indirizzo della risorsa in uno dei due formati a seguire, entrambi validi:

      https://open.spotify.com/track/{track_id} (vecchio formato) e/o https://open.spotify.com/intl-it/track/{track_id} (nuovo formato).

      URL: https://open.spotify.com/track/0RbhVoxOZE8BniSgmDbaXV

      Purtroppo mancano le informazioni relative al nome dell'artista e al titolo del brano, almeno nel mio caso.

      Object music estrapolato dall'immagine pubblica di Josh Hickman iOS 15.3.1 con la versione 1.1.0:


      Come si può vedere, è presente il nome dell'artista, il titolo del brano, l'immagine dell'album e l'URL del brano di Spotify.

      BeReal appuntati

      L'API di richiesta della memorie personali: https://mobile-l7.bereal.com/api/feeds/memories-v1/pinned-memories/for-user/<UID>

      SELECT
          crd.entry_ID,
          cr.request_key AS "URL",
          crd.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:\/\/mobile[-\w]*\.bereal\.com\/api\/feeds\/memories-v(\d)+\/pinned-memories\/for-user\/[\w-]+$"

      La risposta in JSON della memoria entry_ID=26:

      L'array pinnedMemories:

      • [0].takenAt2024-12-28T17:36:09.434Z (data di creazione nel formato ISO 8601 28 dicembre 2024 17:36:09);
      • [0].memoryDay2024-12-28 (data del momento del BeReal nel formato YYYY-MM-DD 28 dicembre 2024);
      • [0].primary: (fotocamera primaria, in genere quella posteriore)
        • mediaType: image (image=foto o video=video);
        • urlhttps://cdn-us1.bereal.network/Photos/LRHP7nD4UhfzEJacyRtuZE37txxx/post/xfbzj038hsmlIEc0.webp (URL dell'immagine);
        • width1500 (larghezza dell'immagine);
        • height2000 (altezza dell'immagine);
      • [0].secondary: (fotocamera secondaria, in genere quella frontale)
        • mediaType: image (image=foto o video=video);
        • urlhttps://cdn-us1.bereal.network/Photos/LRHP7nD4UhfzEJacyRtuZE37txxx/post/qKg3ejQRQIx_CP_R.webp (URL dell'immagine);
        • width1502 (larghezza dell'immagine);
        • height2000 (altezza dell'immagine);
      • [0].isLatefalse (il BeReal è successivo allo slot temporale di due minuti per postare?);
      • [0].isMaintrue (il BeReal è quello principale? true=main, false=bonus);
      • [0].momentIdW_FryMaPGgv9WIR1wF6GX (identificatore univoco della memoria);
      • [0].idJqBYXSaKEoBK7G4-3ZMpe (identificatore univoco del BeReal).

      L'autore del BeReal è quello della richiesta API, nello specifico <UID>=LRHP7nD4UhfzEJacyRtuZE37txxx (Django).

      L'indice dell'array indica la posizione del pin nella schermata: 0=primo elemento.

      Commenti sui BeReal

      I commenti come descritto nell'articolo precedente iOS BeReal - Photos & Friends Daily possono essere estrapolati da vari JSON: per la Cache.db, dai post delle memorie e delle persone.

      La risposta in JSON della memoria entry_ID=79:

      I commenti sul BeReal posts[i]l'array comments:

      • [0].postedAt2025-01-05T11:06:08.305Z (data del commento nel formato ISO 8601 05 gennaio 2025 11:06:08);
      • [0].user (informazioni sull'autore):
        • idLRHP7nD4UhfzEJacyRtuZE37txxx (identificativo univoco dell'autore);
        • usernamedjango.f (nome utente dell'autore);
        • typeUSER (tipo di profilo USER=regular, REAL_BRAND=marchi e REAL_PERSON=celebrità);
        • profilePicture: (immagine del profilo):
          • urlhttps://cdn-us1.bereal.network/Photos/LRHP7nD4UhfzEJacyRtuZE37txxx/profile/CBEd9NNTW-igELqojHZm6.webp (URL dell'immagine del profilo dell'autore);
      • [0].contentTest commento semplice (testo);
      • [0].userIdLRHP7nD4UhfzEJacyRtuZE37txxx (identificativo univoco dell'autore);
      • [0].postId: Ta07G9ad8uTuGlmL-6KiX (identificatore univoco del BeReal);
      • [0].idzmd1UrmumdwRIcXK2pxxx (identificatore univoco del commento).

      La risposta in JSON della persona (vecchio archivio):

      I commenti sul BeReal beRealOfTheDay.userPosts.posts[1]l'array comments:

      • [1].postedAt2025-01-31T14:25:28.486Z (data del commento nel formato ISO 8601 31 gennaio 2025 14:25:28);
      • [1].user (informazioni sull'autore):
        • idLRHP7nD4UhfzEJacyRtuZE37txxx (identificativo univoco dell'autore);
        • usernamedjango.f (nome utente dell'autore);
        • typeUSER (tipo di profilo USER=regular, REAL_BRAND=marchi e REAL_PERSON=celebrità);
        • profilePicture: (immagine del profilo):
          • urlhttps://cdn-us1.bereal.network/Photos/LRHP7nD4UhfzEJacyRtuZE37txxx/profile/CBEd9NNTW-igELqojHZm6.webp (URL dell'immagine del profilo dell'autore);
      • [1].contentIl pollice è del Talano 🤣 (testo);
      • beRealOfTheDay.userPosts.posts[1].idO8YAuxdnIVJRLTgE2Y470 (identificatore univoco del BeReal);
      • [1].idn1RlO5u7KyMaSKhtpWf-C (identificatore univoco del commento).

      RealMojis

      Discorso analogo ai commenti, i RealMojis possono essere estrapolati dai post delle persone e delle memorie.

      La risposta in JSON della persona (vecchio archivio):

      Le reazioni sul BeReal beRealOfTheDay.userPosts.posts[1]l'array realMojis:

      • [0].postedAt2025-01-31T14:24:55.795Z (data della reazione nel formato ISO 8601 31 gennaio 2025 14:24:55);
      • [0].emoji: 👍 (emoji);
      • [0].media: (informazioni sul selfie)
        • url: https://cdn-us1.bereal.network/Photos/LRHP7nD4UhfzEJacyRtuZE37txxx/realmoji/JVgRDw52ClLUWdDe.webp (URI del RealMoji);
      • [0].user (informazioni sull'autore):
        • idLRHP7nD4UhfzEJacyRtuZE37txxx (identificativo univoco dell'autore);
        • usernamedjango.f (nome utente dell'autore);
        • typeUSER (tipo di profilo USER=regular, REAL_BRAND=marchi e REAL_PERSON=celebrità);
        • profilePicture: (immagine del profilo):
          • urlhttps://cdn-us1.bereal.network/Photos/LRHP7nD4UhfzEJacyRtuZE37txxx/profile/CBEd9NNTW-igELqojHZm6.webp (URL dell'immagine del profilo dell'autore);
      • [0].id: AabwT3kVJva-4kfsqVxxx (identificatore univoco del RealMoji).

      La risposta in JSON della memoria differisce di poco dalla precedente e comunque per avere un visione completa delle strutture, basta fare riferimento al modulo BeReal.py del progetto iLEAPP.

      EntitiesStore.sqlite

      Come anticipato nell'introduzione, durante l'analisi della cache delle varie estrazioni, ho tenuto d'occhio questo database EntitiesStore.sqlite; seppur non popolato, dall'ispezione della struttura era indubbia la bontà del suo valore.

      Di seguito sono riportate solo le query con i risultati.

      Account

      SELECT
          U.Z_PK AS "U_PK",
          U.ZPROFILETYPE,
          U.ZFULLNAME,
          U.ZUSERNAME,
          U.ZPROFILEPICTUREURL,
          U.ZBIO,
          U.ZID
      FROM ZUSERMO AS "U"
      WHERE U.ZID = "LRHP7nD4UhfzEJacyRtuZE37txxx"

      Amici

      SELECT
          U.Z_PK AS "U_PK",
          U.ZPROFILETYPE,
          U.ZFULLNAME,
          U.ZUSERNAME,
          U.ZPROFILEPICTUREURL,
          U.ZBIO,
          U.ZID
      FROM ZUSERMO AS "U"
      WHERE U.ZID != "LRHP7nD4UhfzEJacyRtuZE37txxx"

      Post

      SELECT
          U.Z_PK AS "U_PK",
          P.Z_PK AS "P_PK",
          U.Z_PK AS "U_PK",
          PM.Z_PK AS "PM_PK",
          SM.Z_PK AS "SM_PK",
          PTM.Z_PK AS "PTM_PK",
          STM.Z_PK AS "STM_PK",
          M.Z_PK AS "M_PK",
          L.Z_PK AS "L_PK",
          datetime(P.ZTAKENAT + 978307200, 'unixepoch') AS "taken_at",
          P.ZISMAIN AS "is_main",
          CASE
              WHEN P.ZISLATE = 0 AND P.ZISMAIN = 1 THEN "onTime"
              WHEN P.ZISLATE = 1 AND P.ZISMAIN = 1 THEN "late"
              WHEN P.ZISLATE = 0 AND P.ZISMAIN = 0 THEN "onTime bonus"
              WHEN P.ZISLATE = 1 AND P.ZISMAIN = 0 THEN "late bonus"
          END AS "case_type",
          U.ZID || " (" || coalesce(U.ZFULLNAME, U.ZUSERNAME) || ")" AS "author",
          PM.ZMEDIATYPE AS "pm_type",
          PM.ZURL AS "pm_url",
          SM.ZMEDIATYPE AS "sm_type",
          SM.ZURL AS "sm_url",
          PTM.ZMEDIATYPE AS "ptm_type",
          PTM.ZURL AS "ptm_url",
          STM.ZMEDIATYPE AS "stm_type",
          STM.ZURL AS "stm_url",
          M.ZARTIST || " - " || M.ZTRACK AS "song_title",
          M.ZOPENURL AS "song_url",
          P.ZCAPTION,
          L.ZLATITUDE,
          L.ZLONGITUDE,
          P.ZRETAKECOUNTER,
          P.ZLATEINSECONDS,
          P.ZVISIBILITIES,
          (P.ZRESHAREDFROM > 0) AS "reshared",
          --tagged friends
          P.ZMOMENTID,
          P.ZID AS "bereal_id"
      FROM ZPOSTMO AS "P"
      LEFT JOIN ZUSERMO AS "U" ON (P.ZUSER = U.Z_PK)
      LEFT JOIN ZPOSTMO_MEDIAMO AS "PM" ON (P.ZPRIMARYMEDIA = PM.Z_PK)
      LEFT JOIN ZPOSTMO_MEDIAMO AS "SM" ON (P.ZSECONDARYMEDIA = SM.Z_PK)
      LEFT JOIN ZPOSTMO_MEDIAMO AS "PTM" ON (P.ZPRIMARYTHUMBNAIL = PTM.Z_PK)
      LEFT JOIN ZPOSTMO_MEDIAMO AS "STM" ON (P.ZSECONDARYTHUMBNAIL = STM.Z_PK)
      LEFT JOIN ZPOSTMO_MUSICMO AS "M" ON (P.ZMUSIC = M.Z_PK)
      LEFT JOIN ZPOSTMO_LOCATIONMO AS "L" ON (P.ZLOCATION = L.Z_PK)

      Da evidenziare, che per quanto riguarda l'object music, in questo database è presente la tabella ZPOSTMO_MUSICMO con tutti i dettagli dell'audio.

      Commenti

      SELECT
          C.Z_PK AS "C_PK",
          P.Z_PK AS "P_PK",
          U.Z_PK AS "U_PK",
          datetime(C.ZCREATIONDATE + 978307200, 'unixepoch') AS "created_at",
          P.ZID AS "bereal_id",
          IIF(U.ZID = C.ZUSERID, "Outgoing", "Incoming") AS "direction",
          U.ZID || " (" || U.ZUSERNAME || ")" AS "owner",
          C.ZUSERID || " (" || C.ZUSERNAME || ")" AS "author",
          C.ZTEXT,
          P.ZMOMENTID AS "moment_id",
          C.ZID AS "comment_id"
      FROM ZPOSTMO_COMMENTMO AS "C"
      LEFT JOIN ZPOSTMO AS "P" ON (C.ZPOST = P.Z_PK)
      LEFT JOIN ZUSERMO AS "U" ON (P.ZUSER = U.Z_PK)

      RealMojis

      SELECT
          R.Z_PK AS "R_PK",
          P.Z_PK AS "P_PK",
          U.Z_PK AS "U_PK",
          datetime(R.ZDATE + 978307200, 'unixepoch') AS "reaction_date",
          P.ZID AS "bereal_id",
          IIF(U.ZID = R.ZUSERID, "Outgoing", "Incoming") AS "direction",
          U.ZID || " (" || U.ZUSERNAME || ")" AS "owner",
          R.ZUSERID || " (" || R.ZUSERNAME || ")" AS "author",
          R.ZEMOJI,
          R.ZURL,
          P.ZMOMENTID AS "moment_id",
          R.ZID AS "realmoji_id"
      FROM ZPOSTMO_REALMOJIMO AS "R"
      LEFT JOIN ZPOSTMO AS "P" ON (R.ZPOST = P.Z_PK)
      LEFT JOIN ZUSERMO AS "U" ON (P.ZUSER = U.Z_PK)

      Non ho dettagliato la descrizione dei campi in quanto sono stati già ampiamente descritti sia sopra che nel precedente articolo. Per maggiori dettagli invito sempre tutti a dare un'occhiata al codice BeReal.py.

      Da fare

      Resta sempre sospesa la struttura degli screenshot 😡. Ho postato dei BeReal pubblici, se qualcuno vuole contribuire alla ricerca, è libero di fare lo screenshot 🙏. Il mio link https://bere.al/django.f.

      iLEAPP 💖

      Come di consueto, sempre a supporto del progetto iLEAPP (iOS Logs, Events, And Plists Parser) di Alexis Brignoni ho aggiornato il plugin BeReal.py che alla data odierna è in “pull request” e comunque disponibile sul mio GitHub.

      Di seguito alcuni screenshot del report di BeReal:

      Read More