mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-19 18:58:03 +02:00
d8032ac824
* docs: rewrite README, RFCs, and CONTRIBUTING * docs: fix Linux storage labels in RFC-006 (Opera/Vivaldi swapped)
121 lines
5.1 KiB
Markdown
121 lines
5.1 KiB
Markdown
# RFC-009: Windows Locked File Bypass
|
|
|
|
**Author**: moonD4rk
|
|
**Status**: Living Document
|
|
**Created**: 2026-04-05
|
|
|
|
## 1. Problem
|
|
|
|
Chromium on Windows sets `PRAGMA locking_mode=EXCLUSIVE` on certain SQLite databases, most notably the `Cookies` file (`Network/Cookies`). This means Chrome's process holds the file open with `dwShareMode=0` (no sharing), preventing any other process from opening it — even for reading.
|
|
|
|
```
|
|
Chrome.exe
|
|
→ CreateFileW("Network/Cookies", ..., dwShareMode=0) // exclusive lock
|
|
→ PRAGMA locking_mode=EXCLUSIVE
|
|
|
|
hack-browser-data.exe
|
|
→ os.ReadFile("Network/Cookies")
|
|
→ ERROR: access denied
|
|
```
|
|
|
|
This is a **Windows-specific problem**. On macOS and Linux, SQLite uses `fcntl`/`flock` advisory locks, which do not prevent other processes from reading the file. The standard copy path works fine on those platforms.
|
|
|
|
## 2. Solution Overview
|
|
|
|
Bypass the exclusive lock using Windows kernel APIs: enumerate system handles to find Chrome's file handle, duplicate it into our process, then read the file contents via memory-mapped I/O. **No admin privileges required.**
|
|
|
|
```
|
|
NtQuerySystemInformation → find Chrome's handle to Cookies file
|
|
→ DuplicateHandle into our process
|
|
→ CreateFileMappingW + MapViewOfFile (read from kernel cache)
|
|
→ write bytes to temp destination
|
|
```
|
|
|
|
## 3. Step-by-Step
|
|
|
|
### 3.1 Enumerate System Handles
|
|
|
|
Call `NtQuerySystemInformation` with `SystemExtendedHandleInformation` (class 64) to get every open handle in the system. The "extended" variant uses `ULONG_PTR` for PIDs and handle values, avoiding truncation on 64-bit Windows.
|
|
|
|
The query starts with a 4 MB buffer and doubles it (up to 256 MB) if the API returns `STATUS_INFO_LENGTH_MISMATCH`.
|
|
|
|
Each entry in the result table:
|
|
|
|
| Field | Size | Description |
|
|
|-------|------|-------------|
|
|
| UniqueProcessID | `uintptr` | Owning process PID |
|
|
| HandleValue | `uintptr` | Handle value in the owning process |
|
|
| GrantedAccess | `uint32` | Access mask |
|
|
| ObjectTypeIndex | `uint16` | Kernel object type |
|
|
|
|
### 3.2 Find the Target Handle
|
|
|
|
For each handle entry:
|
|
|
|
1. `OpenProcess(PROCESS_DUP_HANDLE, pid)` — open the owning process
|
|
2. `DuplicateHandle` — duplicate the handle into our process with `DUPLICATE_SAME_ACCESS`
|
|
3. `GetFileType` — verify it is `FILE_TYPE_DISK` (skip pipes, sockets, etc.)
|
|
4. `GetFinalPathNameByHandleW` — get the full file path
|
|
|
|
### 3.3 Path Matching with Short-Name Tolerance
|
|
|
|
Windows 8.3 short path names (e.g. `RUNNER~1` vs `runneradmin`) cause direct path comparison to fail. The solution extracts a **stable suffix** by stripping everything before `AppData\Local\` or `AppData\Roaming\` and comparing in lowercase:
|
|
|
|
```
|
|
Input: C:\Users\RUNNER~1\AppData\Local\Google\Chrome\...\Network\Cookies
|
|
Suffix: google\chrome\...\network\cookies
|
|
|
|
Input: C:\Users\runneradmin\AppData\Local\Google\Chrome\...\Network\Cookies
|
|
Suffix: google\chrome\...\network\cookies
|
|
|
|
→ match!
|
|
```
|
|
|
|
### 3.4 Read via Memory-Mapped I/O
|
|
|
|
Once we have a duplicated handle to the locked file:
|
|
|
|
```
|
|
| DuplicateHandle (read access) |
|
|
|-------------------------------------------------|
|
|
↓
|
|
| CreateFileMappingW(handle, PAGE_READONLY) |
|
|
|-------------------------------------------------|
|
|
↓
|
|
| MapViewOfFile(mapping, FILE_MAP_READ, fileSize) |
|
|
|-------------------------------------------------|
|
|
↓
|
|
| byte slice from kernel file cache |
|
|
| (includes uncommitted WAL data from Chrome) |
|
|
|-------------------------------------------------|
|
|
↓
|
|
| os.WriteFile(destination, bytes, 0600) |
|
|
|-------------------------------------------------|
|
|
```
|
|
|
|
Memory-mapped I/O reads from the OS kernel's **file cache**, which includes data Chrome has written but not yet checkpointed to disk. This produces a more complete snapshot than a raw `ReadFile`.
|
|
|
|
**Fallback**: if `CreateFileMappingW` fails (e.g., the file is empty or zero-length), falls back to `Seek(0)` + `ReadFile` on the duplicated handle.
|
|
|
|
## 4. Why This Works
|
|
|
|
The key insight is that `dwShareMode=0` only prevents **new** `CreateFileW` calls from opening the file. It does **not** prevent:
|
|
|
|
- `DuplicateHandle` — which creates a copy of an existing handle (Chrome's own handle)
|
|
- `CreateFileMappingW` — which operates on a handle we already own
|
|
- `MapViewOfFile` — which reads from the kernel's page cache
|
|
|
|
This is a documented Windows behavior, not an exploit. The technique requires only standard user privileges because `PROCESS_DUP_HANDLE` access is available for processes owned by the same user.
|
|
|
|
## 5. Limitations
|
|
|
|
- **Performance**: enumerating all system handles is expensive (the system may have 100,000+ handles). The entire table must be scanned to find the target file.
|
|
- **Race condition**: Chrome could close and reopen the file between enumeration and duplication, though this is unlikely for long-lived database files.
|
|
- **Not needed on macOS/Linux**: advisory locking on these platforms does not prevent reading, so the standard `copyFile` path is always sufficient.
|
|
|
|
## Related RFCs
|
|
|
|
| RFC | Topic |
|
|
|-----|-------|
|
|
| [RFC-008](008-file-acquisition-and-platform-quirks.md) | File acquisition lifecycle and session management |
|