The Duck Hunters Guide - Blog #4 - DuckDuckGo Closed Tab Information (Android)

In my last Duck Hunters Guide post I discussed forensic artifacts associated with Tabs that are open in the Android DuckDuckGo Browser, now I will move onto residual artifacts that are left behind when the user closes a tab or clears their tab data.

I will be talking about the same artifacts as previously so if you haven't read my previous post here it is: The Duck Hunters Guide - Blog #3 - DuckDuckGo Open Tab Information (Android) Tab information!

As I have determined already, browser Tab information goes into the Tabs and Tab_selection tables in the app.db SQLite database. When a user closes a tab, the associated information is deleted from the table.

If I query the Tabs table, I currently have 3 open tabs.

app.db location: data\data\com.duckduckgo.mobile.android\databases\

Query location: Android - DuckDuckGo Open Tabs.sql




As this is an SQLite database, there are options for us to potentially recover closed tab information. The app.db utilizes a Rollback Journal in DELETE mode so when the transaction to delete the tab information is executed it commits directly to the database and the journal gets deleted. This means we cannot recover the information from the journal file.

The only scenario where we could potentially recover closed tab info from the rollback journal would be if the browser crashed when the user closed a tab, but before the delete transaction committed. In this scenario a backup of the page that holds the Tabs table data will be present in the rollback journal. The chances of this happening are astronomical, but you never know.

The app.db also has auto_vacuum enabled so there will be no freelist pages.

This leaves our only option to look at the freeblocks and page unallocated space.

My Tabs table is on Page 18 and looking at the page header I do have at least one freeblock on the page and 3 allocated cells which are the 3 currently open tabs. If I look at the cell pointer array the first 6 bytes are the starting offsets for my 3 allocated cells (2-bytes each), but then I have an 'old' entry at the top of page unallocated space which could be a pointer to a record for a closed tab.



Going to the page offset for the first freeblock 3696 (OE 70), I found the structure for a freeblock with its 4-byte header followed by the freeblock content (deleted data). The first 2-bytes 00 00 tell me that it is the only freeblock on the page and the last 2-bytes 01 90 tell me the freeblock is 400 bytes in length including the freeblock header. Unfortunately, the content was zero'd out which means the database uses secure_delete. So far, as we have a freeblock we know there was at least 1 tab that was closed but the information is unrecoverable.



The last place to check is page unallocated space. As auto_vacuum is enabled, pages do not get reused, and when the user clears their tab data or hits the fire button, all records in the Tabs table are secure deleted. Freeblocks will be created that will ultimately absorb the entire cell content area except the last record. When the last record is secure deleted, it sets the start of the cell content area to the page size i.e 4096 which means that by definition freeblocks don't exist. The app does not use the vacuum command so we could see residual freeblock headers in unallocated space even after the user clears their data but no actual data.

Not to forget we do have a cell reference at the top of unallocated space. The 2-byte entry 0E DA means the cell started at page offset 3802. That page offset puts me into the middle of the freeblock. Freeblock expansion occurs when a cell is deleted that is either directly above or below an existing freeblock. With this information we potentially have 1 more tab that was closed.



At the physical level we have found potentially 2+ closed tab, but we don't have any information as far as what they were or if they were tabs closed since the fire button was last used.

Note: Forensic Tools won't report freeblocks that have zero'd content but just like this example there is value in knowing that they are at least there when you are trying to hunt down if there is deleted data or not.

There are some other SQLite forensic techniques we can use to determine if tabs were closed since the fire button was last used by looking at what I like to call the "logical level".

First is ID Analysis. If I look at the rowid field for the Tabs table, I can see that I am missing rowid 2 which confirms at least 1 tab was closed.






What about that second potential closed tab? To answer this, we need to understand a little better the structure of the Tabs table.

The Tabs table uses a composite key to uniquely identify records. A composite key is a combination of multiple columns as the primary key. With this table the TabID (Text) and SourceTabId (Text) are the declared primary keys which make up the composite key. With SQLite the rowid is the true primary key but as the declared primary key is a composite key the rowid does not auto increment so as tabs are closed, the rowid can be reused.


I did write a blog post on ROWID Reuse if you want to learn more: ROWID Reuse in SQLite Databases

All in all, we found evidence that there was at least 1 closed tab by analyzing the logical data and 1 additional tab by analyzing the physical data.

Cached Data

The other location we can look for residual evidence is the cache. In my previous blog post I talked about the tabPreviews and faviconTemp cache folder. Please note that these 2 folders will be cleared if the user has configured auto-clearing of tab data.

tabPreviews

Within the tabPreviews folder there will be a folder for each tab that has been opened. When a tab is closed, we will still have the cached tabPreview until the cache is cleared.

tabPreviews Location: data\data\com.duckduckgo.mobile.android\cache\

In my example, I have 3 Open Tabs, but 5 folders in the tabPreviews Folders. 





The 2 extra folders are for tabs that have been closed. I looked more into the value used for the tab id to see if there is any information we can glean from it, but found out it is a type 4 UUID which means it is randomly generated.

Inside the folder we have the last cached image for the closed tab. The filename is a Unix Milli-seconds timestamp for when the cached image was created.

Closed Tab 1:

Tab ID: 23a15ba1-903d-4778-98db-ce7b5c90efe7
Tab Preview Cached Date: 2025-01-19 21:17:49 UTC
Tab Preview Content: Google.com

Closed Tab 2:

Tab ID: 55a95c0d-83c4-4fd7-8461-df0f482015af
Tab Preview Cached Date: 2025-01-19 20:06:36 UTC
Tab Preview Content: Search results from a search engine



faviconsTemp Cache Folder 

Within the faviconsTemp folder there will be a folder for tab that has been opened. Within this folder is the favicon for a tab. When a tab is closed, we will still have the cached favicon until the cache is cleared. This can help figure out what base website the user was on, especially if it is a well-known website.

faviconsTemp Location: data\data\com.duckduckgo.mobile.android\cache\

Just like the tabPreviews folder, I have folders for the 3 Open Tabs, but 5 folders present.

The additional folders are for Tab ID 23a15ba1-903d-4778-98db-ce7b5c90efe7 and 55a95c0d-83c4-4fd7-8461-df0f482015af which are closed tabs.



Inside the folder we have the favicon for the closed tab. I looked more into the filenames of the favicons but doesn't appear to be anything meaningful. The created/modified date for the png can be used to show when the favicon was cached. This will tie in with the first visit to the specific website in the tab, but the favicon does not get "downloaded" again as the user navigates through the pages on the web site.

Closed Tab 1:

Tab ID: 23a15ba1-903d-4778-98db-ce7b5c90efe7
Favicon Modified Date: 1/19/2025 9:17:43 PM (UTC)
Favicon: Google Logo










Closed Tab 2:

Tab ID: 55a95c0d-83c4-4fd7-8461-df0f482015af
Favicon Modified Date: 1/19/2025 8:03:43 PM (UTC)
Favicon: DuckDuckGo Logo

With this tab, the cached Preview was results from a search engine. Now we have the favicon we can confirm that search engine was DuckDuckGo.









When we bring all the cached information together, we can determine what website the user was last on in a tab before it was closed, and we have a screenshot of the web page at that moment in time.

Closed Tab 1 

Tab ID: 23a15ba1-903d-4778-98db-ce7b5c90efe7
Tab Preview Cached Date: 2025-01-19 21:17:49 UTC
Tab Preview Content: Google.com
Favicon: Google Logo
Favicon Modified Date: 1/19/2025 9:17:43 PM (UTC)

I can tie this back to the browsing history stored in the history.db using dates. The history database stores visit date in a text field as local time. My device is set to EST which is UTC-5. 

The Favicon Modified date matches exactly with the Visit date, whereas the Tab Preview cached date is 6 seconds after the visit date. The reason for this discrepancy is the TabPreview file is created once the page has fully loaded. So, it took 6 seconds for the Google home page to load (probably due to all the trackers which DuckDuckGo automatically blocks).



Closed Tab 2 

Tab ID: 55a95c0d-83c4-4fd7-8461-df0f482015af
Tab Preview Cached Date: 2025-01-19 20:06:36 UTC
Tab Preview Content: Search results from a search engine
Favicon Modified Date: 1/19/2025 8:03:43 PM (UTC)
Favicon: DuckDuckGo Logo

With this closed tab I have 2 dates. The tab preview cached date and favicon modified date. This means that we can tie this back to multiple entries in the history.db

A simple search for url contains duckduckgo with a visit date of '2025-01-19T15' yielded 3 results.

Visit ID 4 is a DuckDuckGo search for 'tiktik news' at 2024-01-19T15:03:33 which is the favicon modified date exactly. (This was when TikTok was due to shut off in the US and I mis-spelled TikTok)

Visit ID 8 is a DuckDuckGo search for 'ticktick alternatives' at 2024-01-19T15:06:20 which is 16 seconds before the tab preview file generated. Looking at the tab preview file the first search result is '12 Great TickTick Alternatives: Top Todo List Managers in 2024" so this would tie the Tab Preview to the Browsing entry. Essentially it took the search engine 16 seconds to search and populate the first page of results. (Again mis-spelled TikTok and probably not the results I was looking for 😂)

Visit ID 6 is also a DuckDuckGo search that was conducted in between those 2 visits in the closed tab, but there is nothing concrete to see which tab that was search was performed in.



Conclusion

Another successful hunt! We found 2 references to closed tabs by looking at the app.db at the phyiscal level and found those 2 tabs in the cache folder. What I have shown here is it possible to recover some information for closed tabs and then tie it back to browsing history. Just be mindful that when the fire button is used, all this information is deleted including for open tabs. Happy Hunting 🍻





Comments

Popular posts from this blog

The Duck Hunters Guide - Android Cheat Sheet

The Duck Hunters Guide - Blog #5 - Bookmarks & Favorites (Android)

SQBite Beta Release