Merge pull request 'CLI: add wallet bruteforce' (#338) from tobtoht/feather:bruteforce into master

Reviewed-on: https://git.featherwallet.org/feather/feather/pulls/338
This commit is contained in:
tobtoht 2021-02-24 10:59:10 +00:00
commit 0a0c610dc6
7 changed files with 271 additions and 15 deletions

View file

@ -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");
}

View file

@ -8,8 +8,9 @@
#include "appcontext.h"
enum CLIMode {
CLIModeExportContacts,
CLIModeExportTxHistory
ExportContacts,
ExportTxHistory,
BruteforcePassword
};
class CLI : public QObject

View file

@ -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<std::string> found_wallets = m_pimpl->findWallets(path.toStdString());

View file

@ -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);

View file

@ -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);
}

122
src/utils/brute.cpp Normal file
View file

@ -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<strategy>(static_cast<int>(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 "";
}

47
src/utils/brute.h Normal file
View file

@ -0,0 +1,47 @@
#ifndef FEATHER_BRUTE_H
#define FEATHER_BRUTE_H
#include <string>
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