Thawing the Ice Age - Mastodon on Android

With the mass migration away from the "bird site" it was time to dig into the new hotness that is Mastodon. Extinct no more (or should I say a bit more populated these days), Mastodon is a federated social network where instances are run independently of each other. I won't bore you with how it works (come join the infosec.exchange instance, it's run by an alpaca!).

For this blog post, I'm going to dive into the Android app and what we get pull back from a forensics perspective that may be useful for investigations. From a file system dump we can get to the Mastodon app path at:

data\data\org.joinmastodon.android

Contents of these folders are pretty bare at this point. There are three main files of interest, the accounts file, the instance details file, and the account database file. I'll break these three down and what I was able to find with my time spent analyzing them.

Account Database

The account database is what contains details about the app usage itself. This is where we will see timeline items, notifications, searches and more. The file can be found at:

data\data\org.joinmastodon.android\databases\*.db

The database file name will be in the format of <INSTANCE_NAME>_<ACCOUNT_ID>.db. For my test account it was "mstdn.social_109428923124491315.db". The file is just a SQLite database so we can open it in DB Browser. We get five tables total, four of which have usable data.

Figure 1: Table structure in the Mastodon DB file

The names are pretty self explanatory but here's a breakdown:
  • home_timeline - a list of all toots/boosts from your followers and yourself
  • notifications_all - all notifications including boosts, favorites, mentions and replies
  • notifications_mentions - notifications that are only mentions or replies
  • recent_searches - your recently searched hashtags or accounts

Home Timeline

The "home_timeline" only has a few columns including:

  • id - this appears to be the unique identifier of a specific toot
  • json - contents and details about a specific toot, the meat of it all
  • flags - TBD, these were always 0

The JSON can be easily read with a viewer, here is a snippet in NotePad++:

Figure 2: Snippet of a toot from the home_timeline table

We can see here details about the account including account handle (with instance if external from your own instance), display name, followers/following counts, ID, bio details, URL. Further down we get the "content" key which contains the actual toot contents. It's mostly HTML formatted if there are links or hashtags. We can strip out the HTML tags to make it more readable using CyberChef.

Figure 3: HTML content stripped in CyberChef

Other details down in the JSON that we can get about the toot include the URL, reply/reblog counts, other settings such as sensitivity, spoilers, muted, pinned, hashtags and even the visibility. This last one may be key in filtering out if the toot was a DM or to the general public. Using json_extract in our SQLite queries we can pull out useful key/values.

Figure 4: Sample timeline query results in DB Browser

There a plenty of fields that can be included but I've chosen a handful for ALEAPP purposes.

Figure 5: Sample output of timeline in ALEAPP

Notifications

Notifications take up two separate tables in the database because that's how they are broken out in the app itself. When you hit the bell icon you two tabs, one for All and one for Mentions, the difference being All includes favorites, follows, and mentions (including replies) where Mentions naturally only shows mentions/replies.

For our purposes we don't really need to do extra work for the mentions table as the entries are duplicative of the "notifications_all" table. Similar to the timeline table we only get a few columns with the most important.
  • id - identifier of the notification entry
  • json - contents of the notification itself including account details, timestamps and much more
  • flags - TBD, always 0 again
  • type - notification type (explained further below)

Figure 6: notifications_all table in DB Browser

Notifications types are as follows:
  • 0 - follow
  • 2 - reply
  • 3 - boost
  • 4 - favorite
As for the JSON block, we can utilize SQLite and json_extract to pull out pertinent details such as timestamps (both notification created and status created), what account the notification came from, the visibility of the original status, and the contents.

Figure 7: Sample query output from notifications_all

With a little further manipulation we can get these into a nice report via ALEAPP.

Figure 8: All notifications report in ALEAPP

Recent Searches

The last table of interest in the database is the "recent_searches" table. We get three columns of similar nature to the previous ones.
  • id - identifier for the searched item
  • json - the meat of the table, search name along with some historical details
  • time - timestamp in unixepoch of when the search was performed
Figure 9: Sample of the recent_searches table in DB Browser

There are two types of searches that can be performed, either for an account or for a hashtag. Each have their own identifier of sorts in the SQLite database. The "id" column will prepend with "tag" for hashtag search and "acc" for account.

Here is an example of the JSON contents of an account search for the great James Gunn:

Figure 10: JSON contents from an account search

As you can see we get details about the account itself such as handle, display name, account creation date, followers/following counts, bio details, URL, etc.

For the hashtag search we see the JSON as such:

Figure 11: JSON contents from a hashtag search

We get the searched hashtag name, the URL, and a historical usage of the hashtag across the Mastodon instance (I'm not sure yet if it's across all of Mastodon or just the instance the owner belongs to). After some simple SQLite queries we get better viewable results:

Figure 12: Search contents in DB Browser

For ALEAPP I broke out account searches and hashtag searches into separate categories but generally this is what they look like:

Figure 13: Account searches from Mastodon in ALEAPP

I am still working on trying to display the historical hashtag searches but that will come in a future update.

Account JSON file

After the database file, the second file of interest is the "accounts.json" file. It can be found at the path:

data\data\org.joinmastodon.android\files\accounts.json

Inside are details about the user account itself that is logged into the application. Some keys of interest are:

  • username - name of the handle used
  • created_at - timestamp of when the account was created
  • followers_count/following_count - number of followers and following
  • id - unique identifier of the account
  • note - bio section of a profile
We also get alert details which are boolean (true/false) for each type of notification. There are also flags to tell if the account is a bot, suspended, locked, or discoverable.

Figure 14: Account details from accounts.json in ALEAPP

Simple details will help with identifying the local user to the account used on the phone.

Instance Details

The last file we will look at is the file that contains about the instance the user join on Mastodon. The file can be found at the path:

data\data\org.joinmastodon.android\files\instance_*.json

The file name will have be pretended by "instance_" followed by the instance name in all lowercase, replacing periods (".") with underscores ("_"). For my test account I join mstdn.social which leads to the file being named "instance_mstdn_social.json". Inside contains all information for all the custom emojis on the instance which can be a lot!

Items of interest though include the contact account details for the instance, description of the instance, stats such as user counts, URL, and versioning information. Here's what it looks like via ALEAPP:

Figure 15: Mastodon instance details in ALEAPP

This is only the start of what I assume will be a larger impact of investigations as more people start to migrate to the app. All the Mastdon parsers shown here are now live in ALEAPP. Happy tooting! 🐘💨