11 min read

Snapchat - A False Sense Of Security?

Snapchat - A False Sense Of Security?

An Introduction

Incase you're not familiar with Snapchat - Snapchat is a social media application consumers download on their mobile devices, mostly on the premis of...

  • Using the 'disappearing' snaps feature where a user sends a picture/video to a friend which expires after a few seconds.
  • Using the private messaging feature where messages sent between individuals expire after a set amount of time.
  • Using the stories feature where users upload a picture or video which expires 24 hours following the upload of said content.

Basically, everything expires - or does it...

Over the past few days i've spent a considerable amount of time investigating the Snapchat application from a forensic point of view, examining the data it leaves exposed in a 'BFU-available' state on an iOS Device.

Maybe a little cheesy, but I think the real question in this case is what data doesn't Snapchat leave exposed.

If you aren't familiar with the terms BFU, File Protection, PLIST or SQLite, I have published a beginner level article which will provide you with a basic understanding no matter what your technical background may be! :-)

Here's the link for this resource -> iOS Security & Forensics - Taking The First Step

The approach for this research

When examining Snapchat, I had some initial preconceptions around which databases would be useful (having taken a short look at the application a couple of months ago) including arroyo.db & scdb (we'll talk about these later)...

However I decided to take a more holistic approach this time around, as my knowledge of forensics is slowly improving - there may well be artifacts that i'd recognise now that I wouldn't have those couple of months ago!

As we're looking to examine files held within the applications 'container storage', our first job is to copy this data from our research device to our Mac...

For this task, we'll use SCP!

scp -r -P 7788 [email protected]:/private/var/mobile/Containers/Data/Application/5D0DBD47-9E78-4200-B238-CD4FCCACAB8F .

Keep in mind that for this command to work (if you'd like to reproduce this research), you'll need to firstly initialise a USB-SSH tunnel using iProxy or iPhone Tunnel, and replace the GUID with the one representing Snapchat on your device.

The GUID is a long unique string which represents an application internally on a device (so that Applications with the same name cannot cause conflicts) - The device generates the GUID upon app installation and will not be the same upon installation of the same app on another device.

If you need a hand with that process please don't hesitate to get in touch - always happy to help if I can!

So, Files Now Stored Locally - Where Do We Start? Databases!

Without being tempted to aimlessly click around the structure of the container, potentially find a couple of things, and call it a day (definitely not guilty of this!) - we'll begin by searching for databases using the *nix utility 'find'...

Databases often contain large repositories of information, and is preferred by applications for it's format-specific 'querying' abilities - over 'alternatives' such as the plist format (used for storing very small amounts of data - possibly a configuration file which isn't likely to massively vary in size).

We'll be searching for a select couple of extensions: db, sqlite, and sqlite3. These all represent databases types that we can begin exploring and searching for user data within.

find . -name "*.db"
find . -name "*.sqlite"
find . -name "*.sqlite3"

An eagle-eyed reader may have noticed the wildcard '*' at the beginning of each find statement - but why didn't I use one at the end of sqlite to pull both sqlite and sqlite3 results?... Well, it'll match 'write-ahead logs' too! This isn't something we'll explore here today and has been ommited from our find results purposely for that reason.

Executing these commands should pull a few results...

At this point, we're going to open a few of the relevant databases in a GUI-based SQLite reader.

I personally use DB Browser For SQLite which is cross-compatible with other platforms too. A fantastic resource!

arroyo.db

While individually exploring the databases, I bumped into the first 'goldmine' of user information in the Snapchat application...

arroyo.db can be found in the following location within the container:

./Documents/user_scoped/UNIQUE-ID/arroyo/arroyo.db

We'll take a look at the conversation_message table, which, as the table name suggests, contains user private messages. The other tables contained much more generic data without context - not so useful!

conversation_message

In this table alone, I was able to identify the following information:

Column...Contains...
client_conversation_idInternal Conversation Identifier - between two or more individuals. Each private chat/group chat will be assigned it's own identifier to keep track of which messages belong to each 'chat'.
message_contentMessage Data - this column stores a data blob, which when extracted and parsed as a protobuff, contains the text content one user had sent as part of a 'chat'.
creation_timestampMessage Creation/Sent Timestamp - Stored as 'milliseconds since UNIX epoch time' - https://currentmillis.com is a great converter for working with these values.
read_timestampMessage Read Timestamp - Stored as 'milliseconds since UNIX epoch time' - https://currentmillis.com is a great converter for working with these values.
sender_idUnique User Identifier - Used to determine the user which had sent the message for the record in question.
message_state_typeThe sent 'state' of the snap - The states recognised by the current Snapchat version can be found in the 'send_state' table.

Here's an output from my Snapchat processing script 'snapchat-proc.sh' using data gained from this single table...

Using just this data, we can start to build a repository of not only the message data itself, but also the context behind it using the timestamps & conversation identifiers. I'm sure that with a bit of extra work we could build a GUI-based explorer for this data too.

After a few minutes of contemplation while writing this article, I decided I'd save myself the embarrasement of posting the bash script i'd created here, and post the SQL Statements instead (the bash script is definitely due a re-write, but is available in the ZPET repository on my GitHub if you'd like to take a look)

Obtaining Message Data

A pretty simple SQL statement - but important to point out we dump the contents of the message_content column as HEX and not as text data (the protobuf cannot be fully represented if parsed as ASCII - in other words, you'll miss invisible data if you don't dump the data as hex which will result in an invalid protobuff)

select hex(message_content) from conversation_message
Obtaining Sender ID

Nice and simple! Really nothing special happenning here :-)

select sender_id from conversation_message
Sent/Read Timestamps

For this specific artifact, there's a little more process to it. As our timestamp is in milliseconds, we can't directly parse it as UNIX epoch time. In order for us to parse the data correctly, we have to divide the value by 1000 to round the value to seconds.

We're then able to examine a pretty date-time output.

SELECT DATETIME(ROUND(creation_timestamp / 1000), 'unixepoch') AS isodate FROM conversation_message
SELECT DATETIME(ROUND(read_timestamp / 1000), 'unixepoch') AS isodate FROM conversation_message

A few other arroyo.db tables of interest...

Although conversation_message is by far the most interesting table in this database, here are some of my other findings which i've (although of potentially not much forensic value) included here to ensure the completeness of this article.

TableDecription
send_state_typedescribes the different snap 'states' - For example, COMMITED;PREPARING;SENDING;FAILED. States could differ between Snapchat versions on the device. I couldn't see these values changing between versions but could possibly be important following a major feature update.
required_valuesThe 'USERID' is stored as a key in this table, and represents the unique identifier of the currently logged in user.
conversation_identifierThe 'client_conversation_id' column holds the unique conversation IDs you'll see in the 'conversation_message'' table. However there's actually no context to this data, so likely not of much use.

preferences.sqlite

Found at the following location in the container structure:

./Documents/global_scoped/global-scoped-preferences/preferences.sqlite

preferences.sqlite contains some user-specific information, the system boot time (stored as UNIX epoch) at the time of last use, as well as information about the running Snapchat version!

There's a single table of forensic use in preferences.sqlite, let's talk about it!

docprefitem

This is a particularly interesting data-source, or at least I think so. We have two columns of interest - 'p' and 'key'.

We can observe immediately that we're looking at a table designed purely for 'key->value' use. I'm surprised this wasn't designed as a B/PLIST.

I had taken some time to observe 'useful' keys (using both the blob data and the key as a reference) and put together this little table of what you can find...

'Key'Description(Parsed) Blob Data Example
com.snapchat.SCBetaUpdater.lastBetaVersionAlthough The key seems to infer that the value will pertain to Beta versions only, the current release application version number could be observed in the data blob.11.3.5.73
currentOSVersionThe current installed iOS version.14.0
systemBootTimeiOS System Boot Time (stored as UNIX Epoch time) - Populated upon app launch so may not suggest this was the latest time the device was booted.1604678756
SCLastLoginUsernameKeyCurrent logged in Snapchat account username.researchdapit
SCLastLoginUserSCPhoneNumberKeyMobile Number & Country CodeGB 44 7708390975

'Parsed' Blob Data

So, we can extract this data, but there's one small problem. It's not of a known filetype. Here's an output of the 'p' value from the SCLastLoginUsernameKey key...

I couldn't identify it as a protobuff or any type of that kind - but I did find a similarity accross all of the 'p' values that I had manually observed...

The first 69 bytes of the 'p' value, no matter the key, would act as metadata for the blob. Using this data, a full parser could possibly be built - but for now we can strip the first 69 bytes using *NIX utility truncate - here's an example:

tail +69c $count.blob > $count.blob.truncated && mv $count.blob.truncated $count.blob

You could probably also determine a safe amount of bytes to cut from the end of the output too - or just leave it as a method of printing the key without any extra processing :-)

scdb-27.sqlite3

SCDB-27 is a beast when it comes to exposing user data! We'll find a lot of useful data here - a few examples include Sync Tokens, Direct URLs for downloading Snaps, Last Server CheckIn Times & much more!

Let's dive in...

ZGALLERYPROFILE

ZGALLERYPROFILE is a pretty simple table, no obfuscated data for us to sift through - just some simple, easy to interpret values.

ColumnDescriptionExample Data
ZLASTFEATUREDSTORYSYNCTIMEAppears to represent lastest Snapchat Server CheckIn DateTime626384784.507676
ZSYNCTOKENAppears to represent the Apple Push Protocol Sync Token used for retrieving notifications.eyJtYXhT...4NTJ9
ZUSERIDUnique User ID of the logged in Snapchat user.36c76...ef341d-f871a3

ZGALLERYSNAP / ZGALLERYSNAPDETAIL

ZGALLERYSNAP will likely prove very useful from a forensic POV - we can extract a wealth information pertaining to sent snaps using this table.

We're not only able to fetch timestamps for each sent snap, but also...

  • The creation time of each snap as NSDATE (ZCREATETIMEUTC) - https://blog.paddlefish.net/?page_id=90
  • A direct url to access the media (that doesn't seem to expire(yes, really(the media is however encrypted...))) (ZMEDIAREDIRECTURI)
  • The Media Type (ZSERVLETMEDIAFORMAT)
  • The snap 'id' which we can use to identify the conversation to which this snap belongs (ZSNAPID)
  • The sync state for the snap (if saved to the 'memories' Snapchat feature)(ZCLOUDMEDIASTATE)
  • Geo-data from the ZGALLERYSNAPDETAIL table (for the corresponding snap)...

A key peice of data I did NOT find in this database was any sort of record which would assign a sent snap to a particular conversation.

I did try filtering for a timestamp we find here, in conversation_message (arroyo.db) - This however did not pull results due to the other database maintaining only text-based messages.

The SQL statement...

SELECT ZSNAPID, ZSERVLETMEDIAFORMAT, ZCREATETIMEUTC,  ZMEDIAREDIRECTURI FROM ZGALLERYSNAP

Will result in the following, simple view of sent snaps including timestamps and direct URLs.

I've been in search for where the encryption key for the snap data is stored, but haven't found an answer yet. I would love to update this article should anyone have the answer!

In addition to this, we can use the 'Z_PK' value, which is unique to each snap, to check the location data (if found) for said snap. The data will have been acquired by the GeoFilter feature within snapchat and will not pinpoint co-ordinates, but rather the city/nearby attractions.

A Few Malformed File Extensions Later...

So, as it turns out, there are also databases within Snapchat using malformed extensions... Wether this is an attempt at obfuscation or not, i'm not sure!

We can find hidden databases again using the *NIX 'find' utility - this time using '-not -name' to exclude all common extensions for databases.

We'll then instruct 'find' to execute the *NIX utility 'file' on each returned file (everything that's 'not' a database) which will inspect the header to identify it's filetype.

We can then finally grep the output for 'SQLite' - and there we have it! Our hidden databases!

Here's the command you'll use for finding databases with malformed extensions:

find . -not -name "*.sqlite*" -not -name "*.db" -not -name "*wal*" -exec file {} \; | grep SQLite
So, what can we find in these hidden forensic treats?

primary.docobjects

primary.docobjects can be found in the following location within the container structure:

./Documents/user_scoped/dad064d25351ae3d5ab8ac3bb9c2cd31233db6f43b638aed7467f69b5d161d76/DocObjects/primary.docobjects

You will have noticed while reading this article that we're mostly working with unique identifiers, rather than usernames. Using this database, we can pair these values with their associated Snapchat usernames.

arroyomigration__oneononemetadata_draft

The table, quite unusually named 'arroyomigration__oneononemetadata_draft' does actually keep an up to date log of both the unique identifier (in the userID) column, and the Snapchat username in the 'p' column...

The 'p' column is, as per the other 'p' values we have explored throughout, in an unusual format - we can simply remove the first 75 bytes in order to retrieve purely the usernames + the user identifier (the same identifiers we see in the other databases e.g arroyo).

userinfo_coreuserdata

Here we are! Exploring the final database we'll be looking at today - userinfo_coreuserdata!

It's a treat.

In this table, yes, you saw it coming, we have some more wonderful 'p' values!

By stripping the first 76 bytes with truncate, we are presented with some cleanly printed values pertaining to our account, such as:

  • Snapchat 'nickname'
  • Account Date Of Birth
  • Account Email Address
  • Account Mobile Number
  • Account Reigon (GB, for example)
  • Account Username

Here's an example output from my 'snapchat-proc.sh' Snapchat processing script.

James 📲PRINTING DATA...
1998-04-12
PRINTING DATA...
PRINTING DATA...
[email protected]
PRINTING DATA...
+447708390975
GB
PRINTING DATA...
researchdapit

Oh, All Of This Data Is BFU Accessible, Too!

All of the databases examined in this article are accessible without a passcode from the user's device, so long as the device is checkm8 vulnerable and checkra1n compatible!

The Processor Script 'snapchat-proc.sh'

The script I have created to automate extracting specific artifacts mentioned in this article is available both via my GitHub for you to execute manually, and also as a ZPETv2 Module (for my FOSS iDevice triage software). https://github.com/DuffyAPP-IT/ZPET

In Conclusion...

We've explored a variety of databases we'll find within Snapchat for iOS, and there's a few conclusions we can draw from the findings...

  • Snapchat does not make use of NSFileProtection in iOS, which is designed to protect sensitive user information.
  • Snapchat exposes all user registration information including e-mail address, mobile number, DoB and more.
  • Snapchat keeps privately sent media on their servers for an unknown amount of time (beyond two weeks at a minimum) - the direct download links can be utilised without authentication.
  • Snapchat leaves user messages in a decrypted form on device.
  • Snapchat stores both live and expired story data on device in a form we can access from the BFU state (extensionless images, find using *NIX find & file!)

Thank you for reading!

-J