Skip to content

Instantly share code, notes, and snippets.

@sergey-cheperis
Created December 9, 2017 20:37
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sergey-cheperis/64f28fd6e3ba69dc2d13dca826c1aa99 to your computer and use it in GitHub Desktop.
Save sergey-cheperis/64f28fd6e3ba69dc2d13dca826c1aa99 to your computer and use it in GitHub Desktop.
Example program to decrypt passwords saved by Firefox for Windows
// 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