From c929a7af40ed31e18f9ab2af7998bef6dabd1347 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Wed, 24 Feb 2021 11:57:35 +0100 Subject: [PATCH] CLI: add wallet bruteforce --- src/cli.cpp | 85 ++++++++++++++++++--- src/cli.h | 5 +- src/libwalletqt/WalletManager.cpp | 5 ++ src/libwalletqt/WalletManager.h | 3 + src/main.cpp | 19 ++++- src/utils/brute.cpp | 122 ++++++++++++++++++++++++++++++ src/utils/brute.h | 47 ++++++++++++ 7 files changed, 271 insertions(+), 15 deletions(-) create mode 100644 src/utils/brute.cpp create mode 100644 src/utils/brute.h diff --git a/src/cli.cpp b/src/cli.cpp index 8384db9..6f236dc 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -5,8 +5,10 @@ // libwalletqt #include "libwalletqt/TransactionHistory.h" +#include "libwalletqt/WalletManager.h" #include "model/AddressBookModel.h" #include "model/TransactionHistoryModel.h" +#include "utils/brute.h" CLI::CLI(AppContext *ctx, QObject *parent) : QObject(parent), @@ -17,23 +19,86 @@ CLI::CLI(AppContext *ctx, QObject *parent) : } void CLI::run() { - if(mode == CLIMode::CLIModeExportContacts || - mode == CLIMode::CLIModeExportTxHistory) { - if(!ctx->cmdargs->isSet("wallet-file")) return this->finishedError("--wallet-file argument missing"); - if(!ctx->cmdargs->isSet("password")) return this->finishedError("--password argument missing"); + if (mode == CLIMode::ExportContacts || mode == CLIMode::ExportTxHistory) + { + if(!ctx->cmdargs->isSet("wallet-file")) + return this->finishedError("--wallet-file argument missing"); + if(!ctx->cmdargs->isSet("password")) + return this->finishedError("--password argument missing"); ctx->onOpenWallet(ctx->cmdargs->value("wallet-file"), ctx->cmdargs->value("password")); } + else if (mode == CLIMode::BruteforcePassword) + { + QString keys_file = ctx->cmdargs->value("bruteforce-password"); + if (!keys_file.endsWith(".keys")) { + return this->finishedError("Wallet file does not end with .keys"); + } + + QStringList words; + if (ctx->cmdargs->isSet("bruteforce-dict")) { + QString data = Utils::barrayToString(Utils::fileOpen(ctx->cmdargs->value("bruteforce-dict"))); + words = data.split("\n"); + } + + if (!ctx->cmdargs->isSet("bruteforce-chars")) { + return this->finishedError("--bruteforce-chars argument missing"); + } + QString chars = ctx->cmdargs->value("bruteforce-chars"); + + brute b(chars.toStdString()); + if (words.isEmpty()) { + qDebug() << "No dictionairy specified, bruteforcing all chars"; + while (true) { + QString pass = QString::fromStdString(b.next()); + if (ctx->walletManager->verifyWalletPassword(keys_file, pass, false)) { + this->finished(QString("Found password: %1").arg(pass)); + break; + } + qDebug() << pass; + } + } + else { + bruteword bb(chars.toStdString()); + bool foundPass = false; + for (auto word: words) { + if (word.isEmpty()) { + continue; + } + bb.setWord(word.toStdString()); + + while (true) { + QString pass = QString::fromStdString(bb.next()); + if (pass == "") { + break; + } + if (ctx->walletManager->verifyWalletPassword(keys_file, pass, false)) { + this->finished(QString("Found password: %1").arg(pass)); + foundPass = true; + break; + } + qDebug() << pass; + } + if (foundPass) { + break; + } + } + + if (!foundPass) { + this->finished("Search space exhausted"); + } + } + } } void CLI::onWalletOpened() { - if(mode == CLIMode::CLIModeExportContacts){ + if(mode == CLIMode::ExportContacts){ auto *model = ctx->currentWallet->addressBookModel(); auto fn = ctx->cmdargs->value("export-contacts"); if(model->writeCSV(fn)) this->finished(QString("Address book exported to %1").arg(fn)); else this->finishedError("Address book export failure"); - } else if(mode == CLIModeExportTxHistory) { + } else if(mode == ExportTxHistory) { ctx->currentWallet->history()->refresh(ctx->currentWallet->currentSubaddressAccount()); auto *model = ctx->currentWallet->history(); auto fn = ctx->cmdargs->value("export-txhistory"); @@ -45,14 +110,14 @@ void CLI::onWalletOpened() { } void CLI::onWalletOpenedError(const QString &err) { - if(mode == CLIMode::CLIModeExportContacts || - mode == CLIMode::CLIModeExportTxHistory) + if(mode == CLIMode::ExportContacts || + mode == CLIMode::ExportTxHistory) return this->finishedError(err); } void CLI::onWalletOpenPasswordRequired(bool invalidPassword, const QString &path) { - if(mode == CLIMode::CLIModeExportContacts || - mode == CLIMode::CLIModeExportTxHistory) + if(mode == CLIMode::ExportContacts || + mode == CLIMode::ExportTxHistory) return this->finishedError("invalid password"); } diff --git a/src/cli.h b/src/cli.h index 24b538e..38271cd 100644 --- a/src/cli.h +++ b/src/cli.h @@ -8,8 +8,9 @@ #include "appcontext.h" enum CLIMode { - CLIModeExportContacts, - CLIModeExportTxHistory + ExportContacts, + ExportTxHistory, + BruteforcePassword }; class CLI : public QObject diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index fb0b0fc..e5427f0 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -225,6 +225,11 @@ bool WalletManager::walletExists(const QString &path) const return m_pimpl->walletExists(path.toStdString()); } +bool WalletManager::verifyWalletPassword(const QString &keys_file_name, const QString &password, bool no_spend_key, uint64_t kdf_rounds) const +{ + return m_pimpl->verifyWalletPassword(keys_file_name.toStdString(), password.toStdString(), no_spend_key, kdf_rounds); +} + QStringList WalletManager::findWallets(const QString &path) { std::vector found_wallets = m_pimpl->findWallets(path.toStdString()); diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 35df670..f7d07fd 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -105,6 +105,9 @@ public: //! checks is given filename is a wallet; Q_INVOKABLE bool walletExists(const QString &path) const; + //! verify wallet password + Q_INVOKABLE bool verifyWalletPassword(const QString &keys_file_name, const QString &password, bool no_spend_key, uint64_t kdf_rounds = 1) const; + //! returns list with wallet's filenames, if found by given path Q_INVOKABLE QStringList findWallets(const QString &path); diff --git a/src/main.cpp b/src/main.cpp index e3d56ba..2d0ace4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -78,6 +78,15 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { QCommandLineOption exportTxHistoryOption(QStringList() << "export-txhistory", "Output wallet transaction history as CSV to specified path.", "file"); parser.addOption(exportTxHistoryOption); + QCommandLineOption bruteforcePasswordOption(QStringList() << "bruteforce-password", "Bruteforce wallet password", "file"); + parser.addOption(bruteforcePasswordOption); + + QCommandLineOption bruteforceCharsOption(QStringList() << "bruteforce-chars", "Chars used to bruteforce password", "string"); + parser.addOption(bruteforceCharsOption); + + QCommandLineOption bruteforceDictionairy(QStringList() << "bruteforce-dict", "Bruteforce dictionairy", "file"); + parser.addOption(bruteforceDictionairy); + auto parsed = parser.parse(argv_); if(!parsed) { qCritical() << parser.errorText(); @@ -92,7 +101,8 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { bool quiet = parser.isSet(quietModeOption); bool exportContacts = parser.isSet(exportContactsOption); bool exportTxHistory = parser.isSet(exportTxHistoryOption); - bool cliMode = exportContacts || exportTxHistory; + bool bruteforcePassword = parser.isSet(bruteforcePasswordOption); + bool cliMode = exportContacts || exportTxHistory || bruteforcePassword; if(cliMode) { QCoreApplication cli_app(argc, argv); @@ -110,12 +120,15 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { if(exportContacts) { if(!quiet) qInfo() << "CLI mode: Address book export"; - cli->mode = CLIMode::CLIModeExportContacts; + cli->mode = CLIMode::ExportContacts; QTimer::singleShot(0, cli, &CLI::run); } else if(exportTxHistory) { if(!quiet) qInfo() << "CLI mode: Transaction history export"; - cli->mode = CLIMode::CLIModeExportTxHistory; + cli->mode = CLIMode::ExportTxHistory; + QTimer::singleShot(0, cli, &CLI::run); + } else if(bruteforcePassword) { + cli->mode = CLIMode::BruteforcePassword; QTimer::singleShot(0, cli, &CLI::run); } diff --git a/src/utils/brute.cpp b/src/utils/brute.cpp new file mode 100644 index 0000000..17056a2 --- /dev/null +++ b/src/utils/brute.cpp @@ -0,0 +1,122 @@ +#include "brute.h" + +brute::brute(const std::string &chars) { + this->chars = chars; + this->len = chars.length(); +} + +std::string brute::next() { + std::string out; + int c = count; + int i; + while (c >= 0){ + i = (c % len); + out = chars[i] + out; + c = (c / len) - 1; + } + count += 1; + return out; +} + +bruteword::bruteword(const std::string &chars) { + this->m_chars = chars; + this->m_currentStrategy = strategy::ReplaceSingle; +} + +void bruteword::setWord(const std::string &word) { + this->m_currentStrategy = strategy::ReplaceSingle; + m_word = word; + resetIndex(); +} + +void bruteword::nextStrategy() { + m_currentStrategy = static_cast(static_cast(m_currentStrategy) + 1); + resetIndex(); +} + +void bruteword::resetIndex() { + iword = 0; + ichar = 0; +} + +std::string bruteword::next() { + std::string out = m_word; + + switch(m_currentStrategy) { + case strategy::ReplaceSingle: { + if (iword >= m_word.length()) { + this->nextStrategy(); + return next(); + } + out[iword] = m_chars[ichar]; + ichar++; + if (ichar == m_chars.length()){ + ichar = 0; + iword++; + } + return out; + } + + case strategy::AppendSingle: { + if (ichar >= m_chars.length()) { + this->nextStrategy(); + return next(); + } + out += m_chars[ichar]; + ichar++; + return out; + } + + case strategy::PrependSingle: { + if (ichar >= m_chars.length()) { + this->nextStrategy(); + return next(); + } + out = m_chars[ichar] + out; + ichar++; + return out; + } + + case strategy::SwitchLetters: { + if (iword+1 >= out.length()) { + this->nextStrategy(); + return next(); + } + char l = out[iword]; + out[iword] = out[iword+1]; + out[iword+1] = l; + iword++; + return out; + } + + case strategy::DeleteSingle: { + if (iword >= out.length()) { + this->nextStrategy(); + return next(); + } + out = out.substr(0, iword) + out.substr(iword+1); + iword++; + return out; + } + + case strategy::AddSingle: { + if (iword+1 >= out.length()) { + this->nextStrategy(); + return next(); + } + out = out.substr(0, iword+1) + m_chars[ichar] + out.substr(iword+1); + ichar++; + if (ichar == m_chars.length()) { + ichar = 0; + iword++; + } + return out; + } + + case strategy::END: { + return ""; + } + } + + return ""; +} \ No newline at end of file diff --git a/src/utils/brute.h b/src/utils/brute.h new file mode 100644 index 0000000..dddab97 --- /dev/null +++ b/src/utils/brute.h @@ -0,0 +1,47 @@ +#ifndef FEATHER_BRUTE_H +#define FEATHER_BRUTE_H + +#include + +class brute { +public: + explicit brute(const std::string &chars); + + std::string next(); + + std::string chars; + std::string current; + int len; + int count = 0; +}; + +class bruteword { +public: + enum strategy { + ReplaceSingle = 0, + AppendSingle, + PrependSingle, + SwitchLetters, + DeleteSingle, + AddSingle, + END + }; + + explicit bruteword(const std::string &chars); + void nextStrategy(); + void setWord(const std::string &word); + + std::string next(); + +private: + void resetIndex(); + + std::string m_chars; + std::string m_word; + strategy m_currentStrategy; + + int iword = 0; + int ichar = 0; +}; + +#endif //FEATHER_BRUTE_H