Browser, email and cloud sync · Lesson 1 of 6
Chromium browsers: history, downloads, cookies and sessions
11 min read
Most Windows computers you examine run at least one browser built on Chromium, and the browser is often where intent becomes visible: what the user searched for, which page they opened, which file they downloaded and from where. This lesson covers the Chromium profile: where it lives, which SQLite and JSON files it holds, how to read visits, transitions and downloads with exact UTC times, what cookie and password encryption means for an examiner, and how to collect the databases so that recent and deleted records are not lost. Cache and private browsing are covered in Lesson 4.3.
One profile format, many browsers
Google Chrome, Microsoft Edge, Brave, Opera and Vivaldi share Chromium's profile format, so the same files, tables and parsers apply to all of them. Each browser keeps a User Data folder, and inside it one folder per profile: Default for the first profile, then Profile 1, Profile 2 and so on.
| Browser | Profile location on Windows |
|---|---|
| Google Chrome | %LOCALAPPDATA%\Google\Chrome\User Data\<Profile> |
| Microsoft Edge | %LOCALAPPDATA%\Microsoft\Edge\User Data\<Profile> |
| Brave | %LOCALAPPDATA%\BraveSoftware\Brave-Browser\User Data\<Profile> |
| Vivaldi | %LOCALAPPDATA%\Vivaldi\User Data\<Profile> |
| Opera | %APPDATA%\Opera Software\Opera Stable\Default (Roaming, not Local; older versions use Opera Stable itself) |
%LOCALAPPDATA% is C:\Users\<user>\AppData\Local, so the path itself ties the profile to a Windows account. Check every profile folder, not only Default: a second profile is a common place for activity the user wanted to keep apart.
Local State, one level up in User Data, is a JSON file for the whole browser. Under profile.info_cache it lists each profile folder with its display name and, for a signed-in profile, the account e-mail in user_name. Under os_crypt.encrypted_key it holds the Base64-encoded key that protects cookies and saved passwords. Decoded, that value starts with the ASCII text DPAPI, followed by a blob protected by the Windows Data Protection API (DPAPI) for that user.
The History database
History is a SQLite database. Chromium stores its times as WebKit time: microseconds since 1601-01-01 UTC (Lesson 3.1). Chrome deletes visits older than about 90 days, so an empty early period may be expiry, not inactivity.
urls and visits
The urls table has one row per distinct URL: id, url, title, visit_count, typed_count (how often the URL was typed in the omnibox, the combined address and search bar) and last_visit_time. It is a summary. The visits table has one row per visit, which is what a timeline needs:
url: theurls.idof the page visited (a foreign key, not text);visit_time: when the visit started (WebKit time);from_visit: thevisits.idof the referring visit, so you can rebuild chains of navigation;transition: how the user arrived;visit_duration: how long the page remained loaded in its tab, in microseconds, until the tab navigated away or closed.
Transition values
The low byte of transition (transition & 0xFF) is the core type; the high bits are qualifiers.
| Core value | Type | Meaning |
|---|---|---|
| 0 | LINK | Followed a link |
| 1 | TYPED | URL typed in the omnibox or chosen from its suggestions |
| 2 | AUTO_BOOKMARK | Opened from a bookmark |
| 5 | GENERATED | Search or other URL generated from omnibox text |
| 7 | FORM_SUBMIT | Result of submitting a form |
| 8 | RELOAD | Page reloaded |
Qualifier bits include 0x01000000 (forward/back), 0x02000000 (from the address bar), 0x10000000 and 0x20000000 (start and end of a redirect chain), 0x40000000 (client redirect) and 0x80000000 (server redirect). So 805,306,368 = 0x30000000 is a LINK that started and ended a chain, and 838,860,805 = 0x32000005 is GENERATED from the address bar. Some tools display values with the top bit set as negative integers; the mask gives the same core type either way. A TYPED visit is stronger evidence of user intent than a LINK visit, which a page or script can trigger.
downloads and downloads_url_chains
The downloads table has one row per download: target_path (final path), current_path (path while downloading, normally equal to target_path when complete), start_time, end_time, received_bytes, total_bytes, state (1 complete, 2 cancelled, 4 interrupted), danger_type (0 means no warning was shown), tab_url and referrer (the page the download started from), mime_type, opened (1 if opened from the browser's download interface) and last_access_time. downloads_url_chains links to it by id and holds one row per chain_index; the highest index is the URL the file actually came from, and lower indexes show the redirects that led there. The file on disk often has a Zone.Identifier stream whose HostUrl should match that final URL (Lesson 1.4).
keyword_search_terms
keyword_search_terms stores search terms typed into the omnibox (term, and normalized_term in lower case) with url_id pointing to the urls row of the results page. Joining it to visits gives the time of each search.
Other profile files
| File | Format | What it answers |
|---|---|---|
..\Local State | JSON | Which profiles exist, their names and signed-in accounts; the encryption key |
History | SQLite | Which pages were visited, when and how; what was downloaded; what was searched |
Shortcuts | SQLite | What text the user typed in the omnibox and which URL it resolved to (omni_box_shortcuts) |
Top Sites | SQLite | Which sites the user visited most |
Favicons | SQLite | Which page URLs had icons loaded; useful to compare with History |
Bookmarks | JSON | What was bookmarked and when (date_added, WebKit time stored as a string) |
Preferences, Secure Preferences | JSON | Signed-in account, default download directory, extension settings |
Login Data | SQLite | Which sites had saved usernames (logins: origin_url, username_value, date_created, times_used) |
Web Data | SQLite | What was typed into web forms (autofill: name, value, count, date_created, date_last_used) |
Network\Cookies | SQLite | Which hosts set cookies, when created, last used and due to expire |
Sessions\ | SNSS | Which tabs and navigations were open in the current or last session |
Extensions\<id>\<version>\ | folders | Which extensions are installed (manifest.json gives name and permissions) |
Not every Chromium time is WebKit time: date_created and date_last_used in the autofill table are Unix seconds. Check the magnitude of every value before converting it.
Sessions
Recent versions keep Session_<n> and Tabs_<n> files in the Sessions folder, where <n> is a WebKit timestamp; older versions used Current Session, Last Session, Current Tabs and Last Tabs in the profile folder. They are SNSS files: the signature SNSS, a 32-bit version, then a series of commands that record windows, tabs and each tab's navigation entries with URLs and titles. They can show pages that were open when the browser closed, including some no longer in History.
Cookies and encryption
Since Chrome 96 the cookie database is Network\Cookies (earlier, Cookies in the profile folder). The cookies table holds creation_utc, host_key, name, path, expires_utc, last_access_utc and the value in encrypted_value, all times in WebKit format. Since Chrome 80, values carry the prefix v10 and are encrypted with AES-256-GCM using the key from Local State, which is itself DPAPI-protected. Decrypting them offline therefore requires the user's DPAPI master key, typically recovered with the user's password. Since Chrome 127 on Windows, app-bound encryption protects new cookies (prefix v20) with an additional key that only a Chrome service running as SYSTEM will release, and only to the Chrome application. Offline decryption of v20 values from a triage collection is much harder, and often impractical. You rarely need cookie values: the host, the name and the three timestamps usually show that an account visited or signed in to a service, and when. Saved passwords in Login Data are also encrypted; the URL, username and dates are in clear text.
Collecting and parsing safely
Collect each database together with its companion files. A rollback journal (History-journal) holds copies of pages from before a change and can contain records since overwritten. A database in write-ahead-log mode has -wal and -shm files; the WAL holds recent changes not yet written to the main file. Collect whatever exists, and work on copies: opening a WAL database can merge the log into it and change the evidence. Chromium builds SQLite with secure delete on, so deleted rows are normally overwritten with zeros in the main file; older copies may survive in the journal or WAL, in Volume Shadow Copies or in unallocated space, and carving them requires a SQLite recovery tool and verification in hex.
DB Browser for SQLite (open read-only) is enough for manual queries. Hindsight (obsidianforensics) parses a whole Chromium profile into one timeline, NirSoft's BrowsingHistoryView reads several browsers at once, and Plaso has Chrome plugins such as chrome_27_history (Lesson 3.3). Verify any parsed value against the raw table.
Worked example: ana's visits and a download
The triage collection holds C:\Users\ana\AppData\Local\Google\Chrome\User Data\Default\History and History-journal. In Local State, profile.info_cache lists only Default, with an e-mail address in user_name. The query is SELECT v.id, u.url, datetime(v.visit_time/1000000 - 11644473600, 'unixepoch') AS utc, v.transition & 0xFF AS core, v.from_visit, v.visit_duration FROM visits v JOIN urls u ON v.url = u.id ORDER BY v.visit_time;. Integer division drops the microseconds; write the result as ISO 8601 with T and Z.
| id | url | UTC | core | from_visit | visit_duration |
|---|---|---|---|---|---|
| 4180 | https://www.google.com/search?q=send+large+files+free | 2026-03-14T14:08:52Z | 5 | 0 | 11,777,903 |
| 4181 | https://transfer.example.com/ | 2026-03-14T14:09:03Z | 0 | 4180 | 43,528,443 |
| 4182 | https://transfer.example.com/upload | 2026-03-14T14:09:47Z | 0 | 4181 | 1,275,384,220 |
Check the key row by hand. visit_time is 13,417,970,987,412,563. Divided by 1,000,000: 13,417,970,987 s since 1601. Minus 11,644,473,600: Unix 1,773,497,387. Since 1,773,446,400 is 2026-03-14T00:00:00Z, the remaining 50,987 s = 14 h (50,400) + 9 min (540) + 47 s, giving 2026-03-14T14:09:47Z. The duration, 1,275,384,220 µs, is 1,275 s or 21 min 15 s, so the page stayed loaded until about 14:31:02Z, which fits the SRUM interval ending 14:31:09Z (Lessons 2.8 and 3.4). keyword_search_terms has send large files free with url_id equal to the urls.id of visit 4180.
The downloads table has a row with target_path C:\Users\ana\Downloads\xfer.exe, start_time 13,417,892,317,250,948 and end_time 13,417,892,320,031,776, received_bytes and total_bytes both 1,482,752, state 1 and danger_type 0. Start: 13,417,892,317 − 11,644,473,600 = 1,773,418,717; minus 1,773,360,000 (2026-03-13T00:00:00Z) leaves 58,717 s = 16 h (57,600) + 18 min (1,080) + 37 s, so 2026-03-13T16:18:37Z. End: 3 s later, 2026-03-13T16:18:40Z. downloads_url_chains gives index 0 https://files.example.net/get/xfer-latest and index 1 https://cdn.files.example.net/b/7f3a/xfer.exe, the source of the file. The end time matches the $SI modified time and the Shimcache time of xfer.exe (Lesson 2.3), the size matches Amcache, and the first run at 16:20:02Z came 82 seconds later. The finding: the Chrome profile of ana's account downloaded xfer.exe from cdn.files.example.net between 16:18:37Z and 16:18:40Z.
Exam focus: Expect to convert WebKit times fromvisits,downloadsorcookiesinto ISO 8601 UTC, to joinvisits.urltourls.idto find the page behind a visit, and to read the final URL fromdownloads_url_chains. Know the transition core types after masking with 0xFF, thatvisit_durationis in microseconds, and which file answers which question, including Local State for profiles and accounts. Remember that autofill times are Unix seconds, not WebKit time.
Key points
- Chrome, Edge, Brave, Opera and Vivaldi share one profile format under
User Data\<Profile>; Local State lists the profiles and holds the DPAPI-protected key. - History:
urlssummarizes each URL;visitsrecords each visit withvisit_time,from_visit,transition(core type = low byte) andvisit_durationin microseconds. downloadsgives paths, times, sizes and state;downloads_url_chainsgives redirects, with the highestchain_indexas the final file URL.- Cookie values are encrypted (v10 with the DPAPI-protected key, v20 app-bound since Chrome 127), but hosts, names and times are in clear text and usually suffice.
- Collect
-journal,-waland-shmfiles with each database and work on copies; secure delete zero-fills deleted rows, so look for older copies in journals, WAL files and shadow copies.