Learn · Mobile forensics

Acquisition

You rarely get every byte of a phone. Know which level of copy you have, what it can contain, and how to avoid changing the databases while you copy them.

1 Three levels of acquisition

Mobile acquisition methods are usually sorted by how deep they reach. Deeper copies contain more, but need more access to the device, and the deepest ones are often impossible on a current, locked phone.

LevelWhat you getWhat it needsDeleted data?
LogicalWhat the OS chooses to export: a backup, or data read through official interfacesUnlocked device, trust/pairing with the computerOnly what is still inside exported files (freeblocks, WAL)
File system (incl. "full file system")The files of the data partition, including app sandboxes and system databasesElevated privileges on the device (root, jailbreak, or a tool that exploits a vulnerability)Yes, inside the files; plus files apps never back up
PhysicalA bit-for-bit copy of the flash storageLow-level access; on modern phones the result is encrypted unless the keys can also be obtainedIn principle unallocated space too, but file-based encryption makes it mostly unreadable

On current phones, "full file system" copies have largely replaced physical images: since the storage is encrypted per file, a decrypted file-system copy is more useful than an encrypted raw dump.

Write it down: the level, the tool and its version, the OS version and the device state (locked, unlocked, after first unlock). A missing message means something very different in a logical backup than in a full file-system copy.

2 Android: adb and the .ab backup

With USB debugging enabled and the computer authorised on the unlocked phone, the Android Debug Bridge gives you a shell and file transfer. adb pull /sdcard/DCIM copies shared storage. The app sandboxes under /data/data/ are not readable for the shell user, so a plain adb pull of an app's databases fails without root.

adb backup was the classic logical method. It writes one .ab file and still exists, but it is deprecated and increasingly empty:

The format itself is simple: four text lines, then a zlib stream that inflates to a tar archive with paths like apps/<package>/db/….

Unpacking the sample backup (builder output)

Try this: the header ends at byte 0x17. Which two bytes follow, and what do they tell you about the compression? Then check the tar listing: which folder would hold the app's SQLite databases?

Root- or exploit-based methods (temporary root, bootloader vulnerabilities, vendor service modes) reach the full file system. They are what commercial extraction tools do behind their buttons. They are device- and version-specific, can modify the device, and must be documented and validated; they are outside the scope of this course.

3 iOS: iTunes/Finder backups and Manifest.db

A trusted computer can make a local backup of an iPhone (Finder on macOS, iTunes or the Apple Devices app on Windows, or open-source tools using the same protocol). The backup is a folder named after the device's UDID:

Info.plistdevice name, model, iOS version, backup date
Manifest.plistbackup settings, IsEncrypted, app list
Manifest.dbSQLite: one row per backed-up file
00/ … ff/256 folders; each file is stored under its SHA-1 fileID

There are no original file names on disk. Manifest.db maps them: each row of its Files table holds the domain (which area of the phone, e.g. AppDomain-<bundle id> or HomeDomain), the relative path inside it, flags (1 file, 2 directory, 4 symbolic link) and a file blob with metadata (size, times, protection class; NULL in this sample).

fileID = SHA-1( domain + "-" + relativePath ) as 40 hex digits stored at = <backup folder>/<first two hex digits of fileID>/<fileID>

Cell 3 is the chat database: fileID 842b3ea268d81c26ede3c216a5fc1b7bebde69bf, so the file sits in folder 84/. Note that the -wal file has a row of its own: a backup copies it as a separate file. The fileID column is the primary key, so SQLite also keeps an index, sorted by fileID. This is what an index leaf page (type 0A) looks like: each cell holds only the key and the rowid.

The Files table and the SHA-1 check (builder output)

Try this: compute the fileID of the preferences file yourself: printf '%s' 'AppDomain-com.example.chatter-Library/Preferences/com.example.chatter.plist' | sha1sum. In which folder of the backup would it be? Find its row in the hex view.

Encrypted or not?

The user can enable "Encrypt local backup" with a password. Then every file, including Manifest.db itself, is encrypted, and Manifest.plist carries the key bag. Encrypted backups contain more than unencrypted ones: saved passwords (keychain items), Wi-Fi settings, website history and Health data are only included when the backup is encrypted. Examiners therefore often prefer an encrypted backup with a known password over an unencrypted one.

4 Copy the database with its -wal and -shm

Most apps run SQLite in WAL mode: new and changed pages go to chat.db-wal first and reach chat.db only at the next checkpoint. chat.db-shm is a shared-memory index into the WAL. Three consequences for acquisition:

  1. The newest data may exist only in the WAL. Copy the main file alone and recent messages are missing.
  2. Old versions of pages live on in the WAL until it is overwritten, including rows that were deleted since. This is often where deleted chats are found (see the WAL page).
  3. Opening the database can destroy that evidence. When the last connection closes, SQLite checkpoints the WAL into the main file and deletes it. A GUI browser or a quick sqlite3 chat.db on the original does exactly that.

The builder output below shows the same SELECT on two copies of the sample chat database. With immutable=1, SQLite reads the main file only and ignores the WAL, so the message with id 8, which so far exists only in the WAL, is missing. With the -wal next to the copy it appears.

Main file only vs. main file + WAL (builder output)
sha256sum chat.db chat.db-wal chat.db-shm # 1. hash the originals cp chat.db* work/ # 2. work on copies sqlite3 'file:work/chat.db?mode=ro' # 3. read-only: no checkpoint on close sqlite3 'file:work/chat.db?immutable=1' # main file only, the WAL is ignored
Rule of thumb: parse the WAL frames yourself (or with a tool that does) before you let SQLite open the database, and never open the original. Hash the copies again afterwards to show what changed.

5 Try it yourself

Answer from the hex views and builder outputs above. Numbers can be typed in decimal or hex (0x…).

Which format version does the sample .ab file declare?

The header is four lines of ASCII text, each ending in 0A. The version is the second line.

Line 1 ends at 0x0E (…55 50 0A, "UP\n"). Line 2 is 35 0A at 0x0F: the ASCII character 0x35 = "5". Format version 5.

Note that this is text, not a binary number: version 10 would be 31 30 0A.

Inside the tar archive of the backup, in which folder of the app are its SQLite databases?

Open "Unpacking the sample backup" in section 2 and read the paths of the tar listing.

The listing shows apps/com.example.chatter/db/chatter.db, 4,096 bytes (4 pages of 1,024). The databases are in db/; sp/ holds the shared preferences (settings.xml).

In which of the 256 folders of the iOS backup is the chat database's -wal file stored? Give the two hex digits.

Find the row whose relativePath ends in -wal in the Files page, and take the first two characters of its fileID.

Cell 4 (rowid 4) has relativePath "Documents/chat.sqlite-wal" and fileID "ff744af15275a34bce2f6fecec9e569e315edf1c" (bytes 66 66 37 34 … = ASCII "ff74…").

The file is stored as ff/ff744af1…, so in folder ff. The main database (fileID 842b3e…) is in 84/: the two files of one database end up far apart.

How many rows of the Files table describe directories (flags = 2)?

Look at the serial type of flags (the 4th serial type in each record header). Serial type 9 is the constant 1 and has no value bytes; serial type 1 is a 1-byte integer that follows in the values.

Record headers (length byte, then fileID, domain, relativePath, flags, file):

Cell 1: 06 5D 47 0D 01 00, and the flags value byte is 02. Cell 2: 06 5D 47 1F 01 00, flags value 02. Cells 3–6 have 09 in the flags position: the constant 1, a file.

So 2 directories: the app's root (empty relativePath, serial type 0D = 13 = empty text) and Documents.

These are the first 32 bytes of the .ab file: the four header lines and the start of the zlib stream. Edit them and watch the decoded header below:

  • Change the compression flag 31 ("1", at 0x11) to 30 ("0"). The zlib group disappears: a tool would now try to read a tar archive directly at 0x18, and fail.
  • Reset, then change the second zlib byte DA (at 0x19) to 9C, then to DB. Which one is still a valid zlib header? The two header bytes, read as a big-endian number, must be a multiple of 31.
  • Reset, then overwrite "none" (6E 6F 6E 65 at 0x13) with 41 45 53 2D. What does the decoder now say about reading the archive?