mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-07-04 21:37:47 +02:00
64836e9b3c
Apply the no-internal-citation and WHY-not-WHAT rules to source comments; correct stale identifiers (NewBrowser, PBKDF2Key) and RFC facts (Yandex ciphertext, Firefox PBKDF2 password).
122 lines
5.1 KiB
Markdown
122 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 |
|
|
|-------|------|-------------|
|
|
| Object | `uintptr` | Kernel object pointer |
|
|
| 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(DUPLICATE_SAME_ACCESS) |
|
|
|-------------------------------------------------|
|
|
↓
|
|
| CreateFileMappingW(handle, PAGE_READONLY) |
|
|
|-------------------------------------------------|
|
|
↓
|
|
| MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0) |
|
|
|-------------------------------------------------|
|
|
↓
|
|
| 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 for any reason, 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 |
|