Created
December 9, 2017 20:37
-
-
Save sergey-cheperis/64f28fd6e3ba69dc2d13dca826c1aa99 to your computer and use it in GitHub Desktop.
Example program to decrypt passwords saved by Firefox for Windows
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Example program to decrypt passwords saved by Firefox for Windows. | |
// | |
// Uses NSS libraries: https://github.com/sergey-cheperis/nss-nspr-windows | |
// Uses JSON library: https://github.com/nlohmann/json | |
// | |
// Compile with MSVC: | |
// cl /I<path-to-nss>\include /EHsc firefox-decrypt.cpp /link /LIBPATH:<path-to-nss>\release\x64\lib | |
// | |
// NSS DLL's should be put to the .exe directory or to a directory in your PATH | |
// | |
// C++ standard library | |
#include <string> | |
#include <iostream> | |
#include <memory> | |
#include <filesystem> | |
#include <fstream> | |
#include <codecvt> | |
namespace fs = std::experimental::filesystem; | |
// NSS/NSPR includes and libs | |
#include <nss/secitem.h> | |
#include <nss/pk11sdr.h> | |
#include <nss/nssb64.h> | |
#include <nss/nss.h> | |
#include <nss/nssutil.h> | |
#include <prerror.h> | |
#pragma comment(lib, "nss3.lib") | |
#pragma comment(lib, "nspr4.lib") | |
// Windows API includes and libs | |
#include <shlobj.h> | |
#include <objbase.h> | |
#pragma comment(lib, "shell32.lib") | |
#pragma comment(lib, "ole32.lib") | |
// JSON library | |
#include "json.hpp" | |
using json = nlohmann::json; | |
// convert UTF-16 to UTF-8 and back | |
std::string w2s(const std::wstring& source) | |
{ | |
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter; | |
return converter.to_bytes(source); | |
} | |
std::wstring s2w(const std::string& source) | |
{ | |
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter; | |
return converter.from_bytes(source); | |
} | |
// NSS exception class. Automatically gets the error text for the recent NSS error. | |
class NssException : public std::exception | |
{ | |
public: | |
static const char* nssErrorMessage(const std::wstring& context) | |
{ | |
auto code = PR_GetError(); | |
auto length = PR_GetErrorTextLength(); | |
std::wstring message; | |
if (length) | |
{ | |
char *buffer = (char*)calloc((length + 1), sizeof(char)); | |
PR_GetErrorText(buffer); | |
message = context + L". Code " + std::to_wstring(code) + L". " + s2w(buffer); | |
free(buffer); | |
} | |
else | |
message = context + L". Code " + std::to_wstring(code) + L". No error message."; | |
// TODO memory leak here: result of _strdup() is never freed | |
return _strdup(w2s(message).c_str()); | |
} | |
NssException(const std::wstring& context) | |
: std::exception(nssErrorMessage(context)) {} | |
}; | |
// deleter for SECItem pointers for using with unique_ptr | |
class SECItemDeleter | |
{ | |
public: | |
void operator()(SECItem* item) | |
{ | |
SECITEM_FreeItem(item, true); | |
} | |
}; | |
// password decryptor | |
class FirefoxDecryptor | |
{ | |
public: | |
FirefoxDecryptor(const std::wstring& profilePath) | |
{ | |
if (NSS_Initialize(w2s(profilePath).c_str(), "", "", SECMOD_DB, NSS_INIT_READONLY) != SECSuccess) | |
throw NssException(L"NSS_Initialize"); | |
} | |
~FirefoxDecryptor() | |
{ | |
NSS_Shutdown(); | |
} | |
std::string decrypt(const std::string& source) | |
{ | |
size_t len = source.length(); | |
std::unique_ptr<SECItem, SECItemDeleter> reply, request; | |
reply = { | |
SECITEM_AllocItem(nullptr, nullptr, 0), | |
SECItemDeleter() | |
}; | |
if (reply == nullptr) | |
throw NssException(L"SECITEM_AllocItem"); | |
request = { | |
NSSBase64_DecodeBuffer(nullptr, nullptr, source.c_str(), (unsigned int)len), | |
SECItemDeleter() | |
}; | |
if (request == nullptr) | |
throw NssException(L"NSSBase64_DecodeBuffer"); | |
if (PK11SDR_Decrypt(request.get(), reply.get(), nullptr) != SECSuccess) | |
throw NssException(L"PK11SDR_Decrypt"); | |
return std::string((const char*)reply->data, reply->len); | |
} | |
}; | |
// SHGetKnownFolderPath wrapper | |
fs::path knownFolderPath(const KNOWNFOLDERID folderId) | |
{ | |
wchar_t *folder; | |
SHGetKnownFolderPath(folderId, 0, nullptr, &folder); | |
std::wstring result = folder; | |
CoTaskMemFree(folder); | |
return result; | |
} | |
// main | |
int main(int argc, char** argv) | |
{ | |
// enumerate profiles | |
for (auto& p : fs::directory_iterator(knownFolderPath(FOLDERID_RoamingAppData) / "Mozilla" / "Firefox" / "Profiles")) | |
{ | |
std::ifstream jsonFile(p.path() / "logins.json"); | |
if (jsonFile.good()) | |
{ | |
json j; | |
jsonFile >> j; | |
auto& logins = j.at("logins"); | |
if (logins.is_array()) | |
{ | |
FirefoxDecryptor decryptor(p.path()); | |
for (auto& login : logins) | |
{ | |
std::cout << "Hostname: " | |
<< login.at("hostname").get<std::string>() << "\n"; | |
std::cout << "Login: " | |
<< decryptor.decrypt(login.at("encryptedUsername").get<std::string>()) << "\n"; | |
std::cout << "Password: " | |
<< decryptor.decrypt(login.at("encryptedPassword").get<std::string>()) << "\n"; | |
std::cout << "\n"; | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment