Chapter 4. Forensic Trace and Data Leakage

Stealing the entire filesystem from an iOS device can give you a sobering look into the sheer quantity of data cached by these devices. Many reasonably secure applications in the App Store don’t leak data on their own, but still suffer from data leaks because they are subject to Apple’s caching, including the keyboard cache (which caches every secure email or other message typed in), the WebKit cache (which caches many web data views displayed in the application), and other facilities working against the security of the application. This isn’t done intentionally, of course, but rather is the side effect of innocently creating a seamless integrated experience. Depending on what other components of iOS your application uses, your application may also be subject to data leakage in many forms, which could result in theft of data from an otherwise secure app.

This chapter contains excerpts from a private law enforcement training manual I use to train federal agents and local police worldwide. Portions have been rewritten and geared toward developers to understand how an attacker might steal otherwise secure data from a device. It’s necessary to have a full understanding of the extent of data that can be stolen by an attacker, and give you (the developer) a list of nooks and crannies to look in to help ensure your application isn’t being compromised by any of iOS’ integration features. In reviewing your own company’s applications, it is strongly recommended that you analyze a full disk image from a device that has been running your apps to scan for forensic trace. You might be surprised to find corporate data you thought was once secure is now bleeding into other areas of the operating system.

Your own application and its data are stored in the Applications folder inside the mobile user’s directory. There, you’ll find all of the information pertaining specifically to your application. This chapter chronicles all of the information you’ll find throughout the rest of the user data disk, which may contain clear text copies of some of your own application’s data. We saw in Chapter 3 how to extract the data described in this chapter.

Some data cannot be helped but written to the caches, and so the only way to ensure that it doesn’t wind up in a clear text copy outside of your application is to know what data gets written, and avoid writing it all together. This chapter identifies many such types of data, so that you can determine the best way to integrate your application into the operating system.

Other forms of data leakage can also affect the security of an application—many of which are within the developer’s control. These range from the handling of geotagged data to failing to properly wipe deleted records from a SQLite database. All of these data leaking scenarios will be covered in this chapter.

Extracting Image Geotags

You’re probably familiar with the capability of iPhone and iPad devices to not only take photos, but tag them with the user’s current location. Geotagging is the process of embedding geographical metadata to a piece of media, and iOS devices do this with photos and movies. Devices with onboard cameras can embed exact longitude and latitude coordinates inside images taken. Geotagging can be disabled when photos are taken, but in many cases, the user may either forget to disable it or fail to realize its consequences. Photos taken through a third-party application don’t, by default, cause geotags to be written to pictures, but an application could use the GPS to obtain the user’s location and add the tags itself. Sending photos from a user’s library to an insecure network destination will result in these tags being sent as well.

If your application saves geotags when using the camera, this data may be leaked into the photo reel. This could prove problematic for applications running in secure facilities, such as government agencies and secure research facilities with SCIFs.

Exifprobe is a camera image file utility developed by Duane Hesser. Among its features is the ability to extract an image’s exif tags. Download Exifprobe from

To check an image for geotags, call exifprobe on the command line:

% exifprobe -L filename.jpg

If the image was tagged, you’ll see a GPS latitude and longitude reported, as shown here:

JPEG.APP1.Ifd0.Gps.LatitudeRef                 = 'N'
JPEG.APP1.Ifd0.Gps.Latitude                    = 42,57.45,0
JPEG.APP1.Ifd0.Gps.LongitudeRef                = 'W\000'
JPEG.APP1.Ifd0.Gps.Longitude                   = 71,32.9,0

The longitude and latitude coordinates are displayed here as degrees, minutes, and seconds. To convert this to an exact location, add the degree value to the minute value divided by 60. For example:

57.45 / 60 = 0.9575 + 42 = 42.9575
32.9 / 60 = 0.54833 + 71 = 71.54833

In this example, the photo was taken at 42.9575,-71.54833.

On a Mac, the Preview application includes an inspector that can be used to graphically pinpoint the location without calculating the tag’s GPS value. To do this, open the image and select Inspector from the Tools menu. Click the information pane, and the GPS tag, if present, will appear, as shown in Figure 4-1. Clicking on the locate button at the bottom of the inspector window will display the coordinates using the Google Maps website.

GPS coordinates in Preview’s Inspector
Figure 4-1. GPS coordinates in Preview’s Inspector

You’ll also find tags showing that the image was definitively taken by the device’s built-in camera. If the image was synced from a desktop (or other source), the tag may describe a different model camera, which may also be useful:

JPEG.APP1.Ifd0.Make                        = 'Apple'
JPEG.APP1.Ifd0.Model                       = 'iPhone'

The timestamp that the actual photo was taken can also be recovered in the image tags, as shown below:

JPEG.APP1.Ifd0.Exif.DateTimeOriginal            = '2008:07:26 22:07:35'
JPEG.APP1.Ifd0.Exif.DateTimeDigitized           = '2008:07:26 22:07:35'

Consolidated GPS Cache

The consolidated GPS cache can be found as early as iOS 4 and is located in /private/var/root/Caches/locationd/consolidated.db. This cache contains two sets of tables: one set of harvest tables, fed into the device from Apple, and one set of location tables, sent to Apple (Figure 4-2). The harvest tables assist with positioning of the device. The WifiLocation and CellLocation tables contain information cached locally by the device and include WiFi access points and cellular towers that have come within range of the device at a given time, and include a horizontal accuracy (in meters), believed to be a guesstimate at the distance from the device. A timestamp is provided with each entry.

The WifiLocations table provides a number of MAC addresses corresponding to access points seen at the given coordinates. This too can be useful in pinpointing the location of a device at a given time, and also help to determine which access points were within range. Regardless of whether the user connected to any given wireless network, the MAC address and location could still be harvested when the GPS is active. This should be of particular concern when activating the GPS within wireless range of a secure facility.

The data in these tables do not suggest that the device’s owner connected to, or was even aware of the towers or access points within range. The device itself, rather, builds its own internal cache, which it later sends to Apple to assist with positioning. Think of this cache as a war-driving cache, and each GPS-enabled iOS device as Apple’s personal war driver.

A sample consolidated GPS cache from an iOS 4.2 device
Figure 4-2. A sample consolidated GPS cache from an iOS 4.2 device

SQLite Databases

Apple iOS devices make heavy use of database files to store information such as address book contacts, SMS messages, email messages, and other data of a sensitive nature. This is done using the SQLite database software, which is an open source, public domain database package. SQLite databases typically have the file extension .sqlitedb, but some databases are given the .db extension, or other extensions as well.

Whenever an application transfers control to one of Apple’s preloaded applications, or uses the SDK APIs to communicate with other applications’ frameworks, the potential exists for data to leak, as these databases are used extensively through Apple’s software. Consider an enterprise Exchange server with confidential contact information. Such data could potentially be compromised simply by storing this data in the iOS address book, which will expose the otherwise-encrypted data to an attacker.

In order to access the data stored in these files, you’ll need a tool that can read them. Good choices include:

  • The SQLite command-line client, which can be downloaded at

  • SQLite Browser, a free open source GUI tool for browsing SQLite databases. It is available at This tool provides a graphical interface to view SQLite data without issuing direct SQL statements (although knowledge of SQL helps).

Mac OS X includes the SQLite command-line client, so we’ll use command-line examples here. SQLite’s command-line utility can easily access the individual files and issue SQL queries against a database.


The basic commands you’ll need to learn will be explained in this chapter. For additional information about Structured Query Language (SQL), read Learning SQL by Alan Beaulieu (O’Reilly).

Connecting to a Database

To open an SQLite database from the command line, invoke the sqlite3 client. This will dump you to an SQL prompt where you can issue queries:

$ sqlite3 filename.sqlitedb
SQLite version 3.4.0
Enter ".help" for instructions

You are now connected to the database file you’ve specified. To disconnect, use the .exit command; be sure to prefix the command with a period. The SQLite client will exit and you will be returned to a terminal prompt:

sqlite> .exit

SQLite Built-in Commands

After you connect to a database, there are a number of built-in SQLite commands you can issue to obtain information or change behavior. Some of the most commonly used commands follow. These are SQLite-specific, proprietary commands, and do not accept a semicolon at the end of the command. If you use a semicolon, the entire command is ignored.


Lists all of the tables within a database. This is useful if you’re not familiar with the database layout, or if you’ve recovered the file through data carving and are not sure which database you’ve connected to. Most databases can be identified simply by looking at the names of the existing tables.

.schema table-name

Displays the SQL statement used to construct a table. This displays every column in the table and its data type. The following example queries the schema for the mailboxes table, which is found inside a database named Protected Index on the device. This file is available once decrypted using the protection class keys, which will be explained in Chapter 5. This database is used to store email on the device:

sqlite> .schema messages
.dump table_name

Dumps the entire contents of a table into SQL statements. Binary data is output as long hexadecimal sequences, which can later be converted to individual bytes. You’ll see how to do this later for recovering Google Maps cached tile images and address book images.

.output filename

Redirects output from subsequent commands so that it goes into a file on disk instead of the screen. This is useful when dumping data or selecting a large amount of data from a table.

.headers on

Turns display headers on so that the column title will be displayed whenever you issue a SELECT statement. This is helpful to recall the purpose of each field when exporting data into a spreadsheet or other format.


Disconnects from the database and exits the SQLite command shell.

Issuing SQL Queries

In addition to built-in commands, SQL queries can be issued to SQLite on the command line. According to the author’s website, SQLite understands “most of the SQL language.” Most of the databases you’ll be examining contain only a small number of records, and so they are generally manageable enough to query using a simple SELECT * statement, which outputs all of the data contained in the table. Although the proprietary SQLite commands we saw in the previous section do not expect a semicolon (;), standard SQL queries do, so be sure to end each statement with one.

If the display headers are turned on prior to issuing the query, the first row of data returned will contain the individual column names. The following example queries the actual records from the mailboxes table, displaying the existence of an IMAP mailbox located at This mailbox contains three total messages, all of which have been read, with none deleted.

sqlite> SELECT * FROM mailboxes;

Important Database Files

The following SQLite databases are present on the device, and may be of interest depending on the needs of the attacker.


These files exist on the user data partition, which is mounted at /private/var on the iPhone. If you’ve extracted the live filesystem from a tar archive using the DataTheft payload example in Chapter 3, you’ll see a private folder in the current working directory you’ve extracted its contents. If you’re using a raw disk image you’ve recovered using the RawTheft payload, the image will be mounted with the name Data and will have a root relative to /private/var.

Address Book Contacts

The address book contains individual contact entries for all of the contacts stored on the device. The address book database can be found at /private/var/mobile/Library/AddressBook/AddressBook.sqlitedb. The following tables are primarily used:


Contains the name, organization, department, and other general information about each contact


Contains a record of recent changes to properties in the contact database and a timestamp of when each was made


Contains various data for each contact, including phone numbers, email addresses, website URLs, and other data for which the contact may have more than one. The table uses a record_id field to associate the contact information with a rowid from the ABPerson table. To query all of the multivalue information for a particular contact, use two queries—one to find the contact you’re looking for, and one to find their data:

sqlite> select ROWID, First Last, Organization, Department, JobTitle, CreationDate, ModificationDate from ABPerson where First = 'Jonathan';
22|Jonathan|O'Reilly Media|Books|Author|234046886|234046890

sqlite> select * from ABMultiValue where record_id = 22;

Notice the property field in the example. The property field identifies the kind of information being stored in the field. Each record also consists of a label to identify how the data relates to the contact. For example, different numbers in the label field of the previous output indicate whether a phone number is a work number, mobile number, etc. The meaning of each number in the label field can be found in the ABMultiValueLabel table. The following output shows the rowid field of that table, which contains the label numbers shown in the previous output, along with its definition. Because rowid is a special column, it must be specifically named; the general SELECT * from command would not return it:

sqlite> select rowid, * from ABMultiValueLabel;

Some multi-value entries contain multiple values themselves. For example, an address consists of a city, state, zip code, and country code. For these fields, the individual values will be found in the ABMultiValueEntry table. This table consists of a parend_id field, which contains a value matching a rowid of the ABMultiValue table.

Each record in the ABMultiValueEntry table consists of a key/value pair, where the key is a numerical identifier describing the kind of information being stored. The individual keys are indexed starting at 1, based on the values stored in the ABMultiValueEntryKey table as shown here:

sqlite> select rowid, * from ABMultiValueEntryKey;

Putting it all together

The following query can be used to cross-reference the data discussed in the previous sections by dumping every value that is related to any other value in another table (this dump is known in mathematics as a Cartesian product). This may be useful for exporting a target’s contact information into a spreadsheet or other database. Use the following commands to dump the address book into a field-delimited text file named AddressBook.txt:

$ sqlite3 AddressBook.sqlitedb
SQLite version 3.4.0
Enter ".help" for instructions
sqlite> .headers on
sqlite> .output AddressBook.txt
sqlite> select Last, First, Middle, JobTitle, Department,
   ...>    Organization,  Birthday, CreationDate,
   ...>    ModificationDate, ABMultiValueLabel.value,
   ...>    ABMultiValueEntry.value, ABMultiValue.value
   ...> from ABPerson, ABMultiValue, ABMultiValueEntry,
   ...>    ABMultiValueLabel
   ...> where ABMultiValue.record_id = ABPerson.rowid
   ...>     and ABMultiValueLabel.rowid = ABMultiValue.label
   ...>     and ABMultiValueEntry.parent_id = ABMultiValue.rowid;
sqlite> .exit

Address Book Images

In addition to the address book’s data, each contact may be associated with an image. This image is brought to the front of the screen whenever the user receives an incoming phone call from the contact. The address book images are stored in /private/var/mobile/Library/AddressBook/AddressBookImages.sqlitedb and are keyed based on a record_id field corresponding to a rowid within the ABPerson table (inside the AddressBook.sqlitedb database). To extract the image data, first use SQLite’s .dump command, as shown in the following example:

$ sqlite3 AddressBookImages.sqlitedb
SQLite version 3.4.0
Enter ".help" for instructions
sqlite> .output AddressBookImages.txt
sqlite> .dump ABFullSizeImage
sqlite> .exit

This will create a text file containing the image data in an ASCII hexadecimal encoding. In order to convert this output back into binary data, create a simple Perl script named, as shown in Example 4-1.


Perl is a popular scripting language known for its ability to easily parse data. It is included by default with Mac OS X. You may also download binaries and learn more about the language at

Example 4-1. Simple ascii-hexadecimal decoder (

use strict;

mkdir("./addressbook-output", 0755);
while(<STDIN>) {
    next unless (/^INSERT INTO/);
    my($insert, $query) = split(/\(/);
    my($idx, $data) = (split(/\,/, $query))[1,5];
    my($head, $raw, $tail) = split(/\'/, $data);
    decode($idx, $raw);

sub decode {
    my($idx, $data) = @_;
    my $j = 0;
    my $filename = "./addressbook-output/$idx.png";
    print "writing $filename...\n";
    next if int(length($data))<128;
    open(OUT, ">$filename") || die "$filename: $!";
    while($j < length($data)) {
        my $hex = "0x" . substr($data, $j, 2);
        print OUT chr(hex($hex));
        $j += 2;

To decode the AddressBookImages.txt database dump, use the Perl interpreter to run the script, providing the dump file as standard input:

$ perl < AddressBookImages.txt

The script will create a directory named addressbook-output, containing a series of PNG images. These images can be viewed using a standard image viewer. The filename of each image will be the record identifier it is associated with in the AddressBook.sqlite database, so that you can associate each image with a contact.

Google Maps Data

The Google Maps application allows iOS to look up directions or view a map or satellite imagery of a particular location. If an application launched the maps application or used the maps interfaces to display a geographical location, a cache of the tiles may be recoverable from the device. The database file /private/var/mobile/Library/Caches/MapTiles/MapTiles.sqlitedb contains image data of previously displayed map tiles. Each record contains an X,Y coordinate on a virtual plane at a given zoom level, and a binary data field containing the actual image data, stored in PNG-formatted images.

The Google Maps application also stores a cache of all lookups performed. The lookup cache is stored at the path /private/var/mobile/Library/Maps/History.plist on the user partition, and can be easily read using a standard text editor. This lookup cache contains addresses, longitude and latitude, and other information about lookups performed.

Recovering the map tiles is a little trickier than retrieving the history, as the data resides in a SQLite database in the same fashion as the address book images. To extract the actual images, first copy the MapTiles.sqlitedb file onto the desktop machine and dump the images table using the command-line client, as follows. This will create a new file named MapTiles.sql, which will contain information about each map tile, including the raw image data:

$ sqlite3 MapTiles.sqlitedb
SQLite version 3.4.0
Enter ".help" for instructions
sqlite> .output MapTiles.sql
sqlite> .dump images
sqlite> .exit

Create a new file named containing the following Perl code. This code is very similar to the address book code used earlier, but it includes the X,Y coordinates and zoom level of each tile in the filename so that they can be pieced back together if necessary. See Example 4-2.

Example 4-2. Map tiles parsing script (

use strict;
use vars qw { $FILE };

$FILE = shift;
if ($FILE eq "") {
    die "Syntax: $0 [filename]\n";


sub parse {
    my($FILE) = @_;
    open(FILE, "<$FILE") || die "$FILE: $!";
    mkdir("./maptiles-output", 0755);
    while(<FILE>) {
        my $j = 0;
        my $contents = $_;
        next unless ($contents =~ /^INSERT /);
        my ($junk, $sql, $junk) = split(/\(|\)/, $contents);
        my ($zoom, $x, $y, $flags, $length, $data) = split(/\,/, $sql);
        $data =~ s/^X'//;
        $data =~ s/'$//;
        my $filename = "./maptiles-output/$x,$y\@$zoom.png";
        next if int(length($data))<128;
        print $filename . "\n";
        open(OUT, ">$filename") || die "$filename: $!";
        print int(length($data)) . "\n";
        while($j < length($data)) {
            my $hex = "0x" . substr($data, $j, 2);
            print OUT chr(hex($hex));
            $j += 2;

Use the script to convert the SQL dump to a collection of PNG images. These will be created in a directory named maptiles-output under the current working directory.

$ perl MapTiles.sql

Each map tile will be extracted and given the name X,Y@Z.png, denoting the X,Y position on a plane and the zoom level; each zoom level essentially constitutes a separate plane.

A public domain script, written by Tim Fenton, can be used to reassemble these individual tiles into actual map images. To do this, create a new directory for each zoom level you want to reassemble and copy the relevant tile images into the directory. Use the following script to rebuild each set of tiles into a single image. Be sure to install ImageMagick on your desktop, as the script makes extensive use of ImageMagick’s toolset. ImageMagick is an extensive collection of image manipulation tools. Install ImageMagick using MacPorts.

$ sudo port install imagemagick

You’ll also need a blank tile to represent missing tiles on the map. This image can be found in the book’s file repository, named blank.png, or you can create your own blank 64x64 PNG image. See Example 4-3 for a script to reconstruct your map tiles.

Example 4-3. Map tiles reconstruction script (

# Script to re-assemble image tiles from Google maps cache
# Written by Tim Fenton; Public Domain

use strict;

my $i = 62;
my $firstRow = 1;
my $firstCol = 1;

my $j;
my $finalImage;

# do a directory listing and search the space
my @tilesListing = `ls −1 *.png`;
my %zoomLevels;
foreach( @tilesListing )
    my $tileName = $_;

    # do a string match
    $tileName =~ /(\d+),(\d+)[@](\d+).png/;

    # only key into the hash if we got a zoom level key
    if( $3 ne "" )
        if ($2 > $zoomLevels{$3}{row_max} || $zoomLevels{$3}{row_max} eq "")
            $zoomLevels{$3}{row_max} = $2;

        if ($2 < $zoomLevels{$3}{row_min} || $zoomLevels{$3}{row_min} eq "")
            $zoomLevels{$3}{row_min} = $2;

        if ($1 > $zoomLevels{$3}{col_max} || $zoomLevels{$3}{col_max} eq "")
            $zoomLevels{$3}{col_max} = $1;

        if ($1 < $zoomLevels{$3}{col_min} || $zoomLevels{$3}{col_min} eq "")
            $zoomLevels{$3}{col_min} = $1;

foreach( keys( %zoomLevels ) )
    print "Row max value for key: $_ is $zoomLevels{$_}{row_max}\n";
    print "Row min value for key: $_ is $zoomLevels{$_}{row_min}\n";
    print "Col max value for key: $_ is $zoomLevels{$_}{col_max}\n";
    print "Col min value for key: $_ is $zoomLevels{$_}{col_min}\n";

foreach( sort(keys( %zoomLevels )) )
    my $zoomKey = $_;

    # output file name
    my $finalImage = `date "+%H-%M-%S_%m-%d-%y"`;
    chomp( $finalImage );
    $finalImage = "_zoomLevel-$zoomKey-" . $finalImage . ".png";

    # loop over the columns
    for( $j = $zoomLevels{$zoomKey}{col_min};
         $j <= $zoomLevels{$zoomKey}{col_max}; $j++ )
        # loop over the rows
        my $columnImage = "column$j.png";
        for( $i = $zoomLevels{$zoomKey}{row_min};
            $i < $zoomLevels{$zoomKey}{row_max}; $i++ )
            my $fileName = "$j,$i\@$zoomKey.png";

            # check if this tile exists
            if( -e $fileName )
                print "$fileName exists!\n";

                # we're past the first image and have something to join
                if( $firstRow == 0 )
                    # rotate the image
                    `convert -rotate 270 $fileName Rot_$fileName`;
                    `convert +append $columnImage Rot_$fileName $columnImage`;
                else # first row
                    `cp $fileName $columnImage`;
                    $firstRow = 0;
            elsif( $firstRow == 1 ) # do this for the first non-existant row
                print "$fileName doesn't exist\n";
                `cp blank.png $columnImage`;
                $firstRow = 0;
            elsif( $firstRow == 0 )
                print "$fileName doesn't exist\n";
                `cp blank.png Rot_$fileName`;
                `convert +append $columnImage Rot_$fileName $columnImage`;

        # now rotate the column we just created
        `convert -rotate 90 $columnImage $columnImage`;
        `rm Rot*`;

        if( $firstCol == 0 )
            `convert +append $finalImage $columnImage $finalImage`;
            `cp $columnImage $finalImage`;
            $firstCol = 0;

    # clean up the temorary files
    `rm column*`;

The resulting image will stitch together all of the map tiles based on the X, Y coordinates they were assigned. When loading this image in an image viewer, you may see tiles missing, which will be represented by the blank.png tile. Tiles can go missing for two reasons. If the tiles were never viewed in the map, you’ll notice large gaps of tiles in the areas that were never viewed. Single tiles missing from within a viewed region, however, suggest that the map was being viewed while the device was in motion along the given route. Because most mobile carriers’ networks have bandwidth limitations, gaps in tiles are likely to appear in increasing quantities as the vehicle moves faster. The resulting pattern not only suggests that the device’s user traveled the route (rather than simply viewing it on the device), but also gives broad hints as to the route and speed at which the user was traveling. In Figure 4-3, the device’s owner traveled along N. Amherst Rd. at about 35 miles per hour. The staggering of the tiles will change depending on speed, network (Edge vs. 3G), and signal strength. Only experimentation can determine the speed as it relates to missing tiles in a given area.

Reassembled map tile image with missing tiles consistent with motion
Figure 4-3. Reassembled map tile image with missing tiles consistent with motion

Calendar Events

Users and third-party applications may create calendar events and alarms. Data synchronized with Exchange can also synchronize calendar events, which can be leaked through the device’s calendar application. To extract all of the target’s calendar events, an attacker will look at /private/var/mobile/Library/Calendar/Calendar.sqlitedb.

The most significant table in this database is the Event table. This contains a list of all recent and upcoming events and their descriptions:

$ sqlite3 Calendar.sqlitedb
SQLite version 3.4.0
Enter ".help" for instructions
sqlite> select rowid, summary, description, start_date, end_date from CalendarItem;

62|Buy 10M shares of AAPL||337737600.0|337823999.0

Each calendar event is given a unique identifier. Also stored is the event summary, location, description, and other useful information. An attacker can also view events that are marked as hidden.

Unlike most timestamps used on the iPhone, which are standard Unix timestamps, the timestamp used here is an RFC 822 timestamp representing the date offset to 1977. The date is, however, slightly different from RFC 822 and is referred to as Mac Absolute Time. To convert this date, add 978307200, the difference between the Unix epoch and the Mac epoch, and then calculate it as a Unix timestamp:

$ date -r `echo '337737600 + 978307200'| bc`
Wed Sep 14 20:00:00 EDT 2011

Call History

If your application initiates phone calls, each call is logged in the call history. The call history stores the phone numbers of the most recent people contacted by the user of the device, regardless of what application the call was initiated from. As newer calls are made, the older phone numbers are deleted from the database, but often remain present in the file itself. Querying the database will provide the live call list, while performing a strings dump of the database may reveal additional phone numbers. This can be particularly useful for an attacker looking for a log of a deleted conversation or if the call log was cleared by the user. The file /private/var/wireless/Library/CallHistory/call_history.db contains the call history:

$ sqlite3 call_history.db
SQLite version 3.4.0
Enter ".help" for instructions
sqlite> .headers on
sqlite> select * from call;

Each record in the call table includes the phone number of the remote party, a Unix timestamp of when the call was initiated, the duration of the call in seconds (often rounded to the minute), and a flag identifying whether the call was an outgoing or incoming call. Outgoing calls will have the low-order bit of the flags set, while incoming calls will have it clear. Therefore, all odd-numbered flags identify outgoing calls and all even-numbered flags identify incoming calls. It’s important to verify this on a different device running the same firmware version, as flags are subject to change without notice, given that they are proprietary values assigned by Apple.

In addition to a simple database dump, performing a strings dump of the file can recover previously deleted phone numbers, and possibly additional information.

$ strings call_history.db

Later on in this chapter, you’ll learn how to reconstruct the individual SQLite data fields for timestamps, or other values, based on the raw record data.

Email Database

All mail stored locally on the device is stored in a SQLite database having the filename /private/var/mobile/Library/Mail/Protected Index. Unlike other databases, this particular file has no extension, but it is indeed a SQLite database. This file contains information about messages stored locally, including sent messages and the trash can. Data includes a messages and a message_data table, containing message information and the actual message contents, respectively. The file Envelope Index, found in the same directory, contains a list of mailboxes and metadata, which may also be useful for an attacker. This data is also available if an Exchange server is synchronized with the device and mail is stored on the device.

All email that is synchronized to an Exchange server, or other compatible enterprise mail servers that integrate into the Mail application, use this database to store messages—making it a very lucrative target for those interested in stealing confidential email.

To obtain a list of mail stored on the device, query the messages table:

$ sqlite3 Protected\ Index
SQLite version 3.4.0
Enter ".help" for instructions
sqlite> select * from messages;
1|"Zdziarski, Jonathan" <>|Foo|"Smith, John" <>||

The message contents for this message can be queried from the message_data table.

sqlite> sqlite> select * from message_data where message_data_id = 1;
1|I reset your password for the server to changeme123. It's the same as everyone else's password :)

To dump the entire message database into single records, these two queries can be combined to create a single joined query:

sqlite> select * from messages, message_data where message_data.message_data_id = messages.rowid;


The email database is another good candidate for string dumping, as deleted records are not immediately purged from the file.

Mail attachments and message files

In addition to storing mail content, mail attachments are often stored on the filesystem. Within the Mail directory, you’ll find directories pertaining to each mail account configured on the device. Walking down this directory structure, you may find a number of accounts whose folders have an Attachments folder, INBOX, folder, and others. When a passcode is used on the device, attachments are similarly encrypted using data protection. You’ll learn how to defeat this encryption in Chapter 5.

You may also find a number of Messages folders. These folders contain email messages downloaded from the server. While many messages are stored in the Protected Index file, you may also find the raw messages themselves stored as files with .emlx extensions in these directories.


The notes database is located at /private/var/mobile/Library/Notes/notes.sqlite and contains the notes stored for the device’s built-in Notes application. It’s one of the simplest applications on the device, and therefore has one of the simplest databases. Corporate employees often use the simplest and least secure application on the device to store the most sensitive, confidential information. With the advent of Siri, notes are even easier to create.

$ sqlite3 notes.sqlite
SQLite version 3.4.0
Enter ".help" for instructions
  ...>     from ZNOTE, ZNOTEBODY where ZNOTEBODY.Z_PK= ZNOTE.rowid;
321554138|Bank Account Numbers|Bank Account Numbers|Bank Account Numbers<div><br></div><div>First Secure Bank</div><div>Account Number 310720155454</div>

In some cases, deleted notes can be easily recovered by performing a strings dump of this database:

$ strings notes.sqlite

Photo Metadata

The file /private/var/mobile/Library/PhotoData/Photos.sqlite contains a manifest of photos stored in the device’s photo album. The Photos table contains a list of photos and their paths on the device, their resolution, and timestamps when the photo was recorded or modified.

$ sqlite3 Photos.sqlite
SQLite version 3.4.0
Enter ".help" for instructions
sqlite> select * from Photo;

The PhotoAlbum table also contains a list of photo albums stored on the device.

sqlite> select * from PhotoAlbum;
1|1000|0|130|saved photos|8+uXBMbtRDCORIYc7uXCCg||PLCameraAlbum

SMS Messages

The SMS message database contains information about SMS messages sent and received on the device. This includes the phone number of the remote party, timestamp, actual text, and various carrier information. The file can be found on the device’s media partition in /private/var/mobile/Library/SMS/sms.db.

$ sqlite3 sms.db
SQLite version 3.4.0
Enter ".help" for instructions
sqlite> .headers on
sqlite> select * from message;
6|2125551234|1213382708|The password for the new cluster at is root / changeme123. I forgot how to change it. That's why I send this information out of band. We should be safe since we have the 123 in the password.|3|0||3|1213382708|38|0|0

Like the call history database, the SMS database also has a flags field, identifying whether the message was sent or received. The value of the low-order bit determines which direction the message was going. Messages that were sent will have this bit set, meaning the flags value will be odd. If the message was received, the bit will be clear, meaning the flags value will be even.

The SMS messages database is also a great candidate for a strings dump, to recover deleted records that haven’t been purged from the file. An example follows of an SMS message that had been deleted for several days, but was still found in the SMS database:

$ strings sms.db
Make sure you delete this as soon as you receive it. Your new password on the server is poohbear9323.

Safari Bookmarks

The file /private/var/mobile/Library/Safari/Bookmarks.db contains a copy of the bookmarks stored in the Safari browser. These can be set inside the Safari application, or synced from a desktop machine. If your application opens remote resources inside a Safari browser window, a user may bookmark this data, and subsequently any confidential information stored in the URL.

$ sqlite3 Bookmarks.db
SQLite version 3.4.0
Enter ".help" for instructions
sqlite> .headers on
sqlite> select title, url from bookmarks;
O'Reily Media|

Safari bookmarks may have been set directly through the device’s GUI, or represent copies of the bookmarks stored on the target’s desktop machine.

SMS Spotlight Cache

The Spotlight caches, found in /private/var/mobile/Library/Spotlight, contain SQLite databases caching both active and long deleted records from various sources. Inside this folder, you’ll find a spotlight cache for SMS messages named The file SMSSeaerchdb.sqlitedb contains a Spotlight cache of SMS messages, names, and phone numbers of contacts they are (or were) associated with. The Spotlight cache contains SMS messages long after they’ve been deleted from the SMS database, and further looking into deleted records within the spotlight cache can yield even older cached messages.

Safari Web Caches

The Safari web browsing cache can provide an accurate accounting of objects recently downloaded and cached in the Safari browser. This database lives in /private/var/mobile/Library/Caches/ Inside this file, you’ll find URLs for objects recently cached as well as binary data showing the web server’s response to the object request, as well as some binary data for the objects themselves. The cfurl_cache_response table contains the response data, including URL, and the timestamp of the request. The cfurl_cache_blob_data table contains server response headers and protocol information. Finally, the cfurl_cache_receiver_data table contains the actual binary data itself. Keep in mind that not all objects are cached here; primarily small images, JavaScript, and other small objects. It is a good place for an attacker to look for trace, nonetheless.

Web Application Cache

The file /private/var/mobile/Library/Caches/ contains a database of cached objects associated with web apps. These typically include images, HTML, JavaScript, style sheets, and other small, often static objects.

WebKit Storage

Some applications cache data in WebKit storage databases. Safari also stores information from various sites in WebKit databases. The /private/var/mobile/Library/WebKit directory contains a LocalStorage directory with unique databases for each website. Often, these local storage databases can also be found within a third party application’s Library folder, and contain some cached information downloaded or displayed in the application. The application or website can define its own local data, and so the types of artifacts found in these databases can vary. The Google website cache may, for example, store search queries and suggestions, while other applications may store their own types of data. It’s good to scan through WebKit caches to find any loose trace information that may be helpful to an adversary.


The voicemail database contains information about each voicemail stored on the device, and includes the sender’s phone number and callback number, the timestamp, the message duration, the expiration date of the message, and the timestamp (if any) denoting when the message was moved to the trash. The voicemail database is located in /private/var/mobile/Library/Voicemail/voicemail.db, while the voicemail recordings themselves are stored as AMR codec audio files in the directory /private/var/mobile/Library/Voicemail/.

$ sqlite3 voicemail.db
SQLite version 3.4.0
Enter ".help" for instructions
sqlite> .headers on
sqlite> select * from voicemail;
trashed_date|flags 1|100067|1213137634|Complete|2125551234|2125551234|

The audio files themselves can be played by any media player supporting the AMR codec. The most commonly used players include QuickTime and VLC.

Reverse Engineering Remnant Database Fields

When file data has aged to the degree that it has been corrupted by overwrites with new files stored on the device, it may not be possible to directly mount the database. For example, old call records from nine months prior may be present on disk only as fragments of the call history database. When this occurs, it may be necessary to reverse engineer the byte values on disk back into their actual timestamp, flag, or other values if it’s important to an adversary.

Using a test device with the same version of operating firmware, control information can be directly inserted into a SQLite database. Because you’ll know the values of the control information being inserted, you’ll be able to identify their appearance and relative location as stored within the file.

Consider the call_history.db database, which contains the device’s call history. Many older copies of the call history database may be present on the device, and each field contains a specific Unix timestamp. To determine the format in which values are stored in the database, mount a live database on a test device and insert your own data as a marker into the fields:

$ sqlite3 call_history.db
SQLite version 3.5.9
Enter ".help" for instructions
sqlite> .headers on
sqlite> select * from call;
sqlite> insert into call(address, date, duration, flags, id) values(123456789,987654321,336699,9,777);

Use values of a length consistent with the data stored on the device. Once they are added, transfer the database file to the desktop machine and open it in a hex editor. You’ll find the address field stored in plain text, giving you an offset to work from. By analyzing the data surrounding the offset, you’ll find the control values you inserted to be at given relative offsets from the clear text data. The four bytes following the actual clear text 123456789, 3A DE 68 B1 (Figure 4-4, line 0x34A0), represent the value inserted into the date field, 987654321. A simple Perl script can be used to demonstrate this.

$ perl -e 'printf("%d", 0x3ADE68B1);'

Similarly, the next three bytes, 05 23 3B, represent the value added to the duration field:

$ perl -e 'printf("%d", 0x05233B);'

And so on. After repeating this process with consistent results, you’ll identify the raw format of the SQLite fields stored in the database, allowing you to interpret the raw fragments on disk back into their respective timestamps and other values.

The SQLite project is open source, and so you can have a look at the source code for the actual SQLite header format at

Raw field data from a call history database
Figure 4-4. Raw field data from a call history database

SMS Drafts

Sometimes even more interesting than sent or received SMS messages are SMS drafts. Drafts are stored whenever an SMS message is typed, and then abandoned. Newer versions of iOS store a large cache of older drafts, providing the user no mechanism to purge them. SMS drafts live in /private/var/mobile/Library/SMS/Drafts. Each draft is contained in its own folder, which is time stamped identifying when the message was typed and then abandoned.

$ ls -lad private/var2/mobile/Library/SMS/Drafts/SMS-5711.draft/message.plist
-rw-r--r--  1 root  staff  442 May  6 08:48 Drafts/SMS-5711.draft/message.plist

$ cat Drafts/SMS-5711.draft/message.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">
  <string>Word has it, we're going to buy 10M shares of AAPL stock on September 14. Get ready for the stock to skyrocket!</string>
  <string> Word has it, we're going to buy 10M shares of AAPL stock on September 14. Get ready for the stock to skyrocket!</string>

Property Lists

Property lists are XML manifests used to describe various configurations, states, and other stored information. Property lists can be formatted in either ASCII or binary format. When formatted for ASCII, a file can be easily read using any standard text editor, because the data appears as XML.

When formatted for binary, a property list file must be opened by an application capable of reading or converting the format to ASCII. Mac OS X includes a tool named Property List Editor. This can be launched by simply double-clicking on a file ending with a .plist extension. Newer version of Xcode view property lists using the DashCode application.

Other tools can also be used to view binary property lists:

Important Property List Files

The following property lists are stored on iOS devices and may contain useful information for an attacker:


The Core Location cache contains cached information about the last time the GPS was used on the device. The timestamp used in this file is created as the time interval from January 1, 2001.


Contains the Google Maps history. This is in XML format and includes the addresses of any direction lookups, longitude and latitude, query name (if specified), the zoom level, and the name of the city or province where the query was made. Example 4-4 shows a sample of the format.

Example 4-4. Cached map lookup for Stachey’s Pizzeria in Salem, NH
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
<plist version="1.0">
                        <string>517 S Broadway # 5 Salem NH 03079</string>
                        <string>Bracken Cir</string>

Various property lists containing configuration information for each application and service on the device. If third-party “jailbreak” applications have been installed on the device, they will also store their own configuration files here. Among these are, which contains the last store search,, which contains a list of synchronized mail accounts (such as Exchange) with usernames, host names, and persistent UUIDs.


A property list containing a list of all installed applications on the device, and the file paths to each application. Much detailed information is available about applications from this file, including whether the application uses a network connection, and even what compiler the application was built with. This can aid in attacking binaries of installed applications.


Contains the DialerSavedNumber, which is the last phone number entered into the dialer, regardless of whether it was dialed or not.


A list of contacts added to the phone application’s favorites list.


A history of recently viewed YouTube videos.


A list of mail accounts configured on the device.


A history of phone numbers and other accounts that have conferenced using FaceTime.


The last longitude and latitude coordinates viewed in the Google Maps application, and the last search query made.


Contains the ICCID and IMSI, useful in identifying the SIM card last used in the device.


A list of recent searches made through Safari. This file does not appear to get erased when the user deletes his browser cache or history, so this file may contain information even if the user attempted to reset Safari.


The timestamp identifying the last time Safari bookmarks were modified.


Contains the Safari web browser history since it was last cleared.


Contains the last state of the web browser, as of the last time the user pressed the Home button, the iPhone was powered off, or the browser crashed. This list contains a list of windows and websites that were open so that the device can reopen them when the browser resumes, and represents a snapshot of the last web pages looked at by the target.


Stored in the root user’s library, this file contains various information about the device and its account holder. This includes the owner’s Apple Store ID, specified with and, time zone information, SIM status, the device name as it appears in iTunes, and the firmware revision. This file can be useful when trying to identify external accounts belonging to the target.


This directory contains property lists with private keys used for pairing the device to a desktop machine. These records can be used to determine what desktop machines were paired and synced with the device. Certificates from this file will match certificates located on the desktop.


Contains a list of previously known WiFi networks, and the last time each was joined. This is particularly useful when the attacker is trying to determine what wireless networks the device normally connects to. This can be used to determine other potential targets for an attacker. Example 4-5 shows the pertinent information found in each WiFi network entry.

Example 4-5. Known WiFi network entry
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">
        <key>Custom network settings</key>
        <key>List of known networks</key>

                        <string>WPA2 Personal</string>


Similar to the list of known WiFi networks, this file contains a cache of IP networking information. This can be used to show that the device had previously been connected to a given service provider. The information contains previous network addresses, router addresses, and name servers used. A timestamp for each network is also provided. Because most networks run NAT, you’re not likely to obtain an external network address from this cache, but it can show that the device was operating on a given network at a specific time.


Specifies whether airplane mode is presently enabled on the device.

Other Important Files

This section lists some other potentially valuable files to an attacker. Depending on what facilities on the device your application uses, some of your data may be written to some of these files and directories.


Contains a standard binary cookie file containing cookies saved when web pages are displayed on the device. These can be a good indication of what websites the user has been actively visiting, and whether he has an account on the site. The Safari history is also important in revealing what sites the user has recently visited, while the cookies file can sometimes contain more long term information.


This directory contains photo albums synced from a desktop machine. Among other directories, you will find a Thumbs directory, which, in spite of its name, appears to contain full size images from the photo album.


Photos taken with the device’s built-in camera, screenshots, and accompanying thumbnails.


In this directory, you’ll find a Thumbnails directory containing screenshots of recently viewed web pages, along with a timestamp of when the thumbnail was made. You’ll also find a property list named RecentSearches.plist, containing the most recent searches entered into Safari’s search bar.


A binary keyboard cache containing ordered phrases of text entered by the user. This text is cached as part of the device’s autocorrect feature, and may appear from entering text within any application on the device. Often, text is entered in the order it is typed, enabling you to piece together phrases or sentences of typed communication. Be warned, however, that it’s easy to misinterpret some of this information, as it is a hodgepodge of data typed from a number of different applications. Think of it in terms of a keyboard logger. To avoid writing data to this cache, turn autocorrect off in text fields whose input should remain private, or consider writing your own keyboard class for your application.


The text displayed may be out of order or consist of various “slices” of different threads assembled together. View it using a hex editor or a paging utility such as less.


The current background wallpaper set for the device. This is complemented with a thumbnail named LockBackgroundThumbnail.jpg in the same directory.


Contains a list of web pages assigned as buttons on the device’s home screen. Each page will be housed in a separate directory containing a property list named Info.plist. This property list contains the title and URL of each page. An icon file is also included in each web clip directory.


Location of all music synced with the device.


Screenshots of the most recent states of applications at the time they were suspended (typically by pressing the Home button or receiving a phone call). Every time an application suspends into the background, a snapshot is taken to produce desired aesthetic effects. This allows attackers to view the last thing a user was looking at, and if they can scrape deleted files off of a raw disk image, they can also file multiple copies of the last thing a user was looking at. Third-party applications have their own snapshot cache inside their application folder. You’ll learn how to prevent unwanted screen captures from being made later on in this book.


A property list containing a manifest of all system and user applications loaded onto the device through iTunes, and their disk paths.


A cached copy of the data stored on the device’s clipboard. This happens when text is selected and the Cut or Copy buttons are tapped, and can happen from within any application that allows Copy/Paste functionality.


A directory containing screenshots of the last active browser pages viewed with WebKit. If your third-party application displays web pages, reduced versions of these pages may get cached here. Even though the sizes are reduced, however, much of the text can still be readable. This is a particular problem with secure email and banking clients using WebKit, as account information and confidential email can be cached here.


Contains voice recordings stored on the device.


Your application may be secure, but the many features integrated into the operating system are working against your application’s privacy. Apple’s iOS devices are known for their aesthetically pleasing form and quality of their human interface. To achieve this, enormous amounts of data are cached in order to make access quicker and more convenient to the user later. As a result, seamless integration with the operating system and other applications on the device make security a challenge, as data is often copied outside of an application.

In Chapter 11, you’ll learn techniques to write applications more securely so as to thwart forensic evidence from accumulating on devices.

Get Hacking and Securing iOS Applications now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.