In this blog I will discuss my findings on the AppIntent files that are located within the Biomes folder in many iOS extractions. These files contain many forensic artifacts that may no longer appear elsewhere on the device including deleted iMessages. This is my first blog entry and what actually pushed me to finally start a blog, so your constructive feedback is welcome.
Apple seems to have starting storing data in the /private/var/mobile/Library/Biome/streams/public/ folder at some point. This article focuses specifically on the AppIntent subfolder which relates to SiriKit. Within these files you will find data representing the INInteraction class, which contains within it a child of the INIntent class which often is an INSendMessageIntent but there may be a few others. Apple explains that app developers can create an INInteraction to donate relevant interactions to the system. They state that “donating interactions provides contextual information that might be helpful to other apps.” It appears that the files within the /private/var/mobile/Library/Biome/streams/public/AppIntent/local/ are INInteraction objects serialized with the Protobuf format. There is a bplist contained within the protobuf data that represents some child of the INIntent class. I wrote a parser that will read these files and decode quite a bit. The current version of this parser was written before I fully understood the entire file format so I’m in the process of updating it. If you don’t go on to read the whole article you might want to at least check out these sections: Header, Individual Records
The one main forensic benefit of these files is that it appears that they contain data that may be otherwise deleted or no longer existing in the app that generated them. For example there are definitely deleted iMessages, and for an app like Snapchat which pretty much automatically deletes everything there is still data related to the activity present in these files although many apps aren’t actually including the message content.
A few months ago a few coworkers and I stumbled upon what appeared to be a text message in a file we hadn’t seen before. This file was located at the path /private/var/mobile/Library/Biome/streams/public/AppIntent/local/ and the filename seemed to be a timestamp. This timestamp appears to be a variation of a cocoa core timestamp. It’s 15 digits long and it appears that if you place a decimal point between the 9th and 10th digit it will convert to a valid date.
The message we found seemed like it was pretty relevant to our investigation so of course we had to look into it further. As we scrolled through the ascii representation of these files we knew there were lots of iMessages included in there just from seeing the strings “iMessage;-;+12348675309” all over the place. We also identified that there were a lot of bplist00 headers strewn about the file. I was assuming that the iMessage data we were seeing must have been within these bplists so now on to figuring out how to get these bplists out.
I knew about bplists, but I wasn’t completely familiar with their structure at this point. I knew I could probably extract the bplist starting from the bplist00 header, but where do I end? Do I go all the way up to the start of the next header? That didn’t seem to work… I also didn’t really know what a bplist footer looked like at this point either… It was time to reach out to someone who knew more. Ian Whiffin to the rescue… He looked at this file with me and said “it looks like embedded bplists and protobuf”. He also realized that immediately prior to the bplist00 header was a varint. Most the time it’s 2 bytes but as I later found out it was sometimes 3 bytes.
Protobuf? Varint? What are these things? I might have heard of them both before but really didn’t know much about them yet. Ian pointed me to his blog post about varints (scroll down to the Intent section). He also pointed us to his bplist (and protobuf) viewer Mushy. We also discovered that this varint preceding the bplist header was indicating the length of the bplist data. So now with some knowledge and tools at our disposal we started manually extracting these bplists and looking at them. We were manually converting the varints, then extracting the bplist based on that varint value as the length. Wow, this was time consuming. Keep in mind we still had no clue just how much data was here and whether it was feasible to just manually extract some interesting things we found or if we needed a better plan.
I started looking at these files a little closer based on what I now knew. I started writing a simple parser that just looked for the bplist00 header, looked back a few bytes and converted those bytes to a varint… I had to account for the 2 bytes but sometimes 3 bytes… I realized that you were able to tell where the varint started because the byte before the varint was always a \x42 or the ascii letter B as you can see in the below screenshot. So I’ll just start at the bplist00, go back until I find the \x42, then read all the bytes between the \x42 and the bplist, decode them as a varint and then extract the bplist starting at the header and for the length of the decoded varint.
This was a bit of the naive/brute force approach, but I knew it would get the job done for the most part anyway. I still hadn’t fully understood the entire structure of these files yet at this time. This seems like the point where I should just introduce you to my parser. Currently it works just as I described above but I plan to update it to work based on the full structure of the file that I will go on to explain next.
I knew there had to be more to the structure of these files so I decided to start at the very top… If I start there and try to account for every byte I should be able to identify a pattern so here it goes…
So it appears the first 52 bytes are a header for the whole file. Records start at the next byte after the header. I’ll break out the header’s 52 bytes below in a table or at least what I have figured out. Feel free to let me know if I missed anything.
|4 bytes||Unsigned Int||Offset of next available record?|
|4 bytes||none||none – but I suppose this could be part of the above in case the integer needed to be longer than what is able to be represented by 4 bytes|
|8 bytes||double (Apple Absolute Time)||some timestamp|
|4 bytes||unknown / int?||Decoded as an int this is always 9|
|15 bytes||ascii||name of filename but also a Cocoa Core timestamp. Place a decimal between the 9th and 10th character|
|17 bytes||none||always 0’s so far but maybe this is a continuation of the above and would make it 32 bytes in total?|
|4 bytes||ascii||SEGB – Seems to be the identifier of the type of file|
Each record has the below structure. The record is padded at the end so that the records lines up on 8 byte boundaries.
|4 bytes||Unsigned Int (Little Endian)||Length of the data|
|4 bytes||Unsigned Int (LE)||Data Flag (1 = Allocated, 3 = Deleted?, 4 = Next Available?)|
|8 bytes||Double (Apple Absolute Time)||Unknown timestamp|
|8 bytes||Double (Apple Absolute Time)||Unknown timestamp|
|4 bytes||Unsigned Int (LE)||Data Flag (10 = Intent?, 0 = Unallocated?)|
|length found above||Protobuf (See below)||Data|
|varies||— Padding —||\x0 padding up until the next 8 byte boundary|
Decoding The Protobuf
So in sample data I extracted the data and used google’s protoc utility’s –decode_raw option to decode as shown below. One issue with protobuf and –decode_raw is that if you don’t have access to the .proto file that was used to create the protobuf stream then you don’t know the name of the fields and they will only display by their ID numbers as shown below. Another issue is that the .proto file is what specifies the kind of data held within each field, so the utility has to make a “best guess” as to what to decode it as. You can see below it did a pretty decent job at guessing.
Lets go through field by field:
- 1 – Apple Absolute Timestamp – Convert to a Double and then to the Timestamp
- 2 – App Id that created the record
- 4 – Class Name (Will go into further detail on this below)
- 5 – Action (Somehow correlates to key 4)
- 6 – Unknown
- 7 – Unknown
- 8 – BPlist containing data we are interested in
Let’s analyze fields 2 and 4 a based on some sample data
|App ID (Field 2)||Classes Names Found in Same Records (Field 4)|
|com.apple.mobiletimer||MTToggleAlarmIntent, INCreateTimerIntent, MTUpdateAlarmIntent, MTCreateAlarmIntent|
|com.apple.Music||INPlayMediaIntent, INIntent, INAddMediaIntent|
We can see that the FindMy app creates LocateDevice intents, the InCallService creates StartCall intents, MobileSMS/Instagram/Snapchat/WhatsApp all create SendMessage intents. We can see that just by matching app names with intent types it seems to make some sense. We really haven’t learned what intents are yet so let’s take a look at Apple Developer documentation on that. First let’s start with the SiriKit page where they give a high level overview of Intents.
The Intents and IntentsUI frameworks drive interactions that start with “Hey Siri…”, Shortcuts actions, and widget configuration. The system also incorporates intents and user activities your app donates into contextual suggestions in Maps, Calendar, Watch complications, widgets, and search results.
Use the standard intents that the system provides to empower actions users already ask Siri to do, such as playing music or sending a text message. You can also offer your app’s unique capabilities throughout the system by designing custom intents. For more details about defining custom intents, see Adding User Interactivity with Siri Shortcuts and the Shortcuts App.
You can process intents directly in your app, or in an Intents app extension.https://developer.apple.com/documentation/sirikit
So intents somehow relate to interactions with Siri. It appears that one of the main purposes of intents is for app developers to integrate their apps abilities into Siri. Apple also mentions that the system “incorporates intents and user activities your app donates into contextual suggestions”. Let’s look at INIntent class.
INIntentclass is abstract and provides behaviors shared by all intent objects. You don’t create instances of this class directly or implement your own custom subclasses. For a list of intent types that SiriKit already handles, see the Standard Intents section of SiriKit. You may also define custom intent types in an Intent Definition file.
Each subclass ofhttps://developer.apple.com/documentation/sirikit/inintent
INIntentdefines the properties needed to perform the corresponding action. You use instances of those classes when responding to a request sent to your app or Intents extension by SiriKit. For more information about a specific type of action, see the appropriate subclass.
So the INIntent class is an “abstract” class, which means that this class essentially sets a template to be used for child classes. Now let’s look at a specific type of INIntent class called INSendMessageIntent which is one that we see often in the above table and relates to many apps that have higher forensic value.
Siri creates an
INSendMessageIntentobject when the user asks to send a message to one or more users. This intent object contains the message to send and the recipients of the message, which can include groups of users. Use the information in this object to construct and send the message.
To handle this intent, the handler object in your Intents extension must adopt thehttps://developer.apple.com/documentation/sirikit/insendmessageintent
INSendMessageIntentHandlingprotocol. Your handler confirms the request and creates an
INSendMessageIntentResponseobject with the results.
So, according to above the INSendMessageIntent object is created when a user asks Siri to send a message. But I know there are received messages in these files and the Siri kit page talked about developers donating user activities, so let’s look at something we haven’t talked about yet: INInteraction
INInteractionobject encapsulates information about a SiriKit request and your app’s response. SiriKit creates interaction objects automatically when it needs your app to respond to a specific intent, either by handling the intent or providing an error explaining why your app couldn’t handle the intent. SiriKit places the interaction in an
NSUserActivityobject that the system passes to your app at launch time. You can also create instances of this class in your app and donate relevant interactions to the system.
Donating interactions provides contextual information that might be helpful to other apps. Some system apps use donated interactions to improve search results or to anticipate user actions. For example, a ride-booking app could donate an interaction containing the user’s planned ride information. If the user subsequently uses the Maps app to search for restaurants, Maps can show relevant results near the user’s destination.
You choose which of your app’s interactions you want to donate to the system. To donate an interaction, create an instance of this class, filling it with your intent object and response, and call thehttps://developer.apple.com/documentation/sirikit/ininteraction
donate(completion:)method. You can also use the methods of this class to delete interactions when they are no longer relevant.
So an INInteraction object is created when SiriKit needs an app to respond to an “Intent”, but developers can also create instances and “donate” them. Let’s take a quick look at an INInteraction constructor.
init(intent: INIntent, response: INIntentResponse?)Initializes and returns an interaction object with an intent object and your app’s response.
You can see that an INInteraction it consists of an INIntent and optionally an INIntentResponse. So a developer can create an INIntent (or one of its child classes for instance INSendMessageIntent) and wrap it in an INInteraction class, and “donate” it to the system. It’s worth noting that the INInteraction class has a few methods related to deleting these previously donated intents from the system. It at least initially appears that some of these apps are not deleting the interactions when the messages are deleted within the app. For example you will find may more of these records for Snapchat than messages exist within the intact Snapchat database. I’ve also confirmed that I am able to see iMessages that are no longer within the intact database.
Analyzing the BPList
Let’s look into that BPList data contained within field “8” from above. I’m going to use Ian’s tool Mushy to view the data. It appears that the data inside this BPList is actually an NSKeyedArchiver where certain keys may refer to other keys noted by a “UID”. Ian recently updated Mushy to be able to link from a UID to its corresponding key to more easily view these types of NSKeyedArchiver files.
Below we will take a look at the root node and see that it contains a UID that points to “1” which is key 1 under the $objects key. We then find a key named “$class” containing a UID of “58”. If we follow that UID we see that there is another key named $classname with the value INInteraction. So it appears the root element in this BPList is an INInteraction.
We learned earlier that according to the Apple Developer documentation that an INInteraction class contains an INIntent (or child) and optionally an INIntentResponse. In the below image we can see that there is in fact a key for both of these with a corresponding UID.
Looking back at the above image it’s also worth noting the presence of the “dateInterval”, “groupIdentifier”, “intentHandlingStatus” and “direction” keys. (All of the fields are defined in the INInteraction documentation which we have looked at before)
“groupIdentifier” in this sample data is the string: iMessage;-;+12348675309 (Just a side note this isn’t where my parser currently pulls this data from)
“direction” possible values are: 0 = Unspecified, 1 = Outgoing, 2 = Incoming (Source: INInteractionDirection)
“intentHandlingStatus” possible values are: 0 = unspecified, 1 = ready, 2 = inProgress, 3 = success, 4 = failure, 5 = deferredToApplication, 6 = userConfirmationRequired (Source: INIntentHandlingStatus)
If you look back above at the dateInterval you can see it points to UID 52. Below is that node and we can see that it has a startDate, endDate and duration. In this case for an iMessage, the start and end dates are the same therefore they both point to the same UID (53) and the duration is 0. This appears to be a DateInterval object.
Let’s dive deeper into the “Intent”. In the below example we can see that this intent is specifically an “INSendMessageIntent”. We can even see the class inheritance that’s occurring. We can see the child class is the INSendMessageIntent, which inherits from the INIntent class which ultimately inherits from the NSObject class (which is the root of most Objective-C class hierarchies)
Now let’s see whats inside the INSendMessageIntent data. First lets look at the backingStore key and follow that to the UID specified.
We can see below that key 4 contains “bytes” and “$class” keys. I collapsed the “bytes” key so that we can quickly look at the $class key first. You can see reference to PB in both the _INPBSendMessageIntent and the PBCodable names. It does not seem like Apple provides any documentation on these classes but we can assume the PB stands for protobuf. The protobuf data is stored inside that collapsed “bytes” key.
This is where things get a little fuzzy because of the way protobuf works. Recall the issues with not having a .proto file. In this case Mushy and any other protobuf parser that does not have access to the .proto file is making a “best guess” and the field names are unknown. Let’s look inside the bytes key now. For the sake of limiting the number of images being included below and the need to sanitize the sample data I’m going to show the next screenshot with the data collapsed but with each key marked as to the data found within it.
It appears that each app that donates these INSendMessageIntent will include different data within its “bytes” key. For example I viewed one related to signal and under the “bytes” key it only had sub keys 1, 7 and 8. The only identifiable data within these keys is the groupIdentifier and group/thread name.
I have also found that these files usually contain about 30 days worth of data. It appears to include deleted messages currently.