The Duck Hunters Guide - Blog #8 - Downloads (Android)
/storage/emulated/0/Download/
The information about files downloaded is stored in the downloads.db SQLite database.
downloads.db location: data\data\com.duckduckgo.mobile.android\databases\
This database is very simple, with only the downloads table containing user data:
- Id - Unique ID for each tab
- downloadId - A 64-bit signed integer that is a unique identifier assigned to each download request.
- downloadStatus - The status of the download. That I have been able to figure out so far
- 0 = Started
- 1 = Completed
- filename - Name of the file downloaded
- contentLength - The size of the file in bytes
- filepath - The download location
- createdAt - The datetime when the download process was initiated. This is a text timestamp stored as local time for the device.
Note: downloadId may appear as a negative number in the database, but it is still a valid signed 64-bit integer. It appears to be negative due to how Java handles signed long values.
As this is only 1 table, this database is super easy to query:
Download Link: Android - DuckDuckGo Downloads.sql
id,
downloadId AS "Download ID",
CASE downloadStatus
WHEN 0 THEN 'Started (' || downloadStatus || ')'
WHEN 1 THEN 'Completed (' || downloadStatus || ')'
ELSE 'Unknown (' || downloadStatus || ')'
END AS "Download Status",
fileName AS "File Name",
ROUND(contentLength / 1024.0, 2) AS "Size (KB)",
filePath AS "Download Path",
DATETIME(createdat) AS "Download Date (Local)"
FROM downloads
In my example, the output shows that 7 files were downloaded. Whilst we don't know where the files were downloaded from, we do know what was downloaded, when they were downloaded and where the downloaded files reside.
I dug a little deeper into the downloads process to determine what occurs if a user cancelled a download, deletes a download, and what happens when the fire button is used to clear all tabs and data.
Here is what I found:
- If a user cancels a download in progress, the entry associated with that download is deleted from the database.
- If a user manually deletes a download using the browser, the entry associated with that download is deleted from the database and the downloaded file is also deleted (As Expected)
- If a user uses the fire button it does not have any effect on the downloads database
Deleted Download Entries
As this is an SQLite database it can be possible to recover records associated with downloads that have been manually deleted or cancelled by the user.
The database does use secure_delete, meaning upon deletion, the freeblocks that are created will have their contents zero'd out. That being said, the database uses write-ahead logging so we can potentially recover some records by looking at the historical WAL frames.
Let's start with ID analysis. The id field in the downloads table is an auto-incrementing integer primary key. This means that whenever a user downloads a file, it is assigned an ID equal to the highest ever used ID plus one. As a result, if a user cancels or deletes a download, gaps will appear in the ID sequence, since those values are never reused.
Querying the sqlite_sequence table:
WHERE name = 'downloads'
This information tells me that the user downloaded 10 files. I only have 7 active records; therefore 3 downloads were either deleted or cancelled.
You can use a tool like MIRF to identify the gaps, or if your highest id value is less than 1000 you can use a common table expression (CTE) to identify the gaps such as this:
Download Link: Identify_Missing_IDs.sql
SELECT 1
UNION ALL
SELECT n + 1 FROM id_values
WHERE n < (SELECT seq FROM sqlite_sequence WHERE name = 'downloads')
)
SELECT n AS missing_ids
FROM id_values
WHERE n NOT IN (SELECT id FROM downloads)
ORDER BY missing_ids;
Running this query, I am missing id's 1,4 & 6.
Now we can parse out the WAL file and look for these 3 missing ids.
Parsing the WAL yielded 91 raw records. Running a SELECT DISTICT to remove duplicates cut that number down to 19.
Looking at the database at this level, each download has 2 records. This is due to the download process where the download status changed from Started (0) to Completed (1) and the size field is populated.
The first version of the record is when the download process started. The second version of the record is when the download has completed.
Note: The date stored in the createdAt field is the recorded time when the download was started and is not updated when the download is completed.
This is a great example of record modification and is very common in SQLite databases, although not necessarily automatically recognized by our tools.
We know in this example there are 3 downloads that are unaccounted for. Running a simple WHERE clause we can filter the output to attempt to find the missing id's
Query: WHERE downloads.id IN (1,4,6)
In this example all 3 missing downloads were found in the WAL file, with a total of 5 records returned.
Let's break each download down to see what happened:
Download ID 1
A download of iRoot_com.mgyun.shua.su.apk started at 2025-05-29 15:15:01 local time and was successfully completed.
As there was a record modification changing the status changed from 0 to 1, it indicates that the download was deleted by user interaction.
Download ID 4
A download of Android_14_Public_Image.tar.gz started at 2025-06-16 15:03:31 local time.
As there was no record modification changing the download status changed from 0 to 1, it indicates that the download was interrupted either by user intervention (cancelling) or the download failed.
Download ID 6
A download of 03._Crypto-currencies_and_the_Future_oF_Money_Author_IE_University.pdf started at 2025-06-16 15:22:07 local time and was successfully completed.
As there was a record modification changing the status changed from 0 to 1, it indicates that the download was deleted by user interaction.
Conclusion
That is another DuckDuckGo artifact in the books. What I have established is we can extract user download history very easily. By looking at the SQLite WAL file there is the possibility of recovering deleted or cancelled downloads. Based on the number of entries for a download you can determine whether the user manually deleted the file or if the file download process didn't complete.
Another successful day of hunting Android DuckDuckGo artifacts 🍻
Comments
Post a Comment