149 lines
4.8 KiB
C++
149 lines
4.8 KiB
C++
// ©AngelaMos | 2026
|
|
// Engine.hpp
|
|
|
|
#pragma once
|
|
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <expected>
|
|
#include <string>
|
|
#include <thread>
|
|
#include "src/attack/BruteForceAttack.hpp"
|
|
#include "src/attack/DictionaryAttack.hpp"
|
|
#include "src/attack/RuleAttack.hpp"
|
|
#include "src/config/Config.hpp"
|
|
#include "src/core/Concepts.hpp"
|
|
#include "src/display/Progress.hpp"
|
|
#include "src/threading/ThreadPool.hpp"
|
|
|
|
class Engine {
|
|
public:
|
|
template <Hasher H, AttackStrategy A>
|
|
static auto crack(const CrackConfig& cfg)
|
|
-> std::expected<CrackResult, CrackError>;
|
|
};
|
|
|
|
template <Hasher H, AttackStrategy A>
|
|
auto Engine::crack(const CrackConfig& cfg)
|
|
-> std::expected<CrackResult, CrackError> {
|
|
unsigned thread_count = cfg.thread_count > 0
|
|
? cfg.thread_count
|
|
: std::thread::hardware_concurrency();
|
|
|
|
ThreadPool pool(thread_count);
|
|
|
|
auto attack_name = [&]() -> std::string_view {
|
|
if (cfg.bruteforce) { return "Brute Force"; }
|
|
if (cfg.use_rules) { return "Rules"; }
|
|
return "Dictionary";
|
|
}();
|
|
|
|
auto total_estimate = [&]() -> std::size_t {
|
|
if constexpr (std::same_as<A, BruteForceAttack>) {
|
|
BruteForceAttack probe(cfg.charset, cfg.max_length, 0, 1);
|
|
return probe.total();
|
|
} else if constexpr (std::same_as<A, RuleAttack>) {
|
|
auto probe = DictionaryAttack::create(cfg.wordlist_path, 0, 1);
|
|
if (!probe) { return 0; }
|
|
return probe->total() * 2005;
|
|
} else {
|
|
auto probe = DictionaryAttack::create(cfg.wordlist_path, 0, 1);
|
|
if (!probe) { return 0; }
|
|
return probe->total();
|
|
}
|
|
}();
|
|
|
|
Progress progress(H::name(), attack_name, thread_count,
|
|
total_estimate, pool.state().found,
|
|
pool.state().tested_count);
|
|
|
|
if (Progress::is_tty()) {
|
|
progress.print_banner();
|
|
std::puts("");
|
|
std::puts("");
|
|
std::puts("");
|
|
}
|
|
|
|
auto start = std::chrono::steady_clock::now();
|
|
|
|
std::jthread display_thread;
|
|
if (Progress::is_tty()) {
|
|
display_thread = std::jthread([&](std::stop_token st) {
|
|
while (!st.stop_requested() &&
|
|
!pool.state().found.load(std::memory_order_relaxed)) {
|
|
progress.update();
|
|
std::this_thread::sleep_for(
|
|
std::chrono::milliseconds(config::PROGRESS_UPDATE_MS));
|
|
}
|
|
});
|
|
}
|
|
|
|
pool.run([&](unsigned tid, unsigned total, SharedState& state) {
|
|
H hasher;
|
|
|
|
auto create_attack = [&]() {
|
|
if constexpr (std::same_as<A, BruteForceAttack>) {
|
|
return std::expected<BruteForceAttack, CrackError>(
|
|
BruteForceAttack(cfg.charset, cfg.max_length, tid, total));
|
|
} else if constexpr (std::same_as<A, RuleAttack>) {
|
|
return RuleAttack::create(
|
|
cfg.wordlist_path, cfg.chain_rules, tid, total);
|
|
} else {
|
|
return DictionaryAttack::create(cfg.wordlist_path, tid, total);
|
|
}
|
|
};
|
|
|
|
auto attack = create_attack();
|
|
if (!attack.has_value()) { return; }
|
|
|
|
while (!state.found.load(std::memory_order_relaxed)) {
|
|
auto candidate = attack->next();
|
|
if (!candidate.has_value()) { break; }
|
|
|
|
std::string to_hash = *candidate;
|
|
if (!cfg.salt.empty()) {
|
|
if (cfg.salt_position == "prepend") {
|
|
to_hash = cfg.salt + to_hash;
|
|
} else {
|
|
to_hash = to_hash + cfg.salt;
|
|
}
|
|
}
|
|
|
|
if (hasher.hash(to_hash) == cfg.target_hash) {
|
|
state.set_result(std::move(*candidate));
|
|
break;
|
|
}
|
|
|
|
state.tested_count.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
});
|
|
|
|
if (display_thread.joinable()) {
|
|
display_thread.request_stop();
|
|
display_thread.join();
|
|
}
|
|
|
|
auto end = std::chrono::steady_clock::now();
|
|
double elapsed = std::chrono::duration<double>(end - start).count();
|
|
auto tested = pool.state().tested_count.load(std::memory_order_relaxed);
|
|
double speed = (elapsed > 0.0) ? static_cast<double>(tested) / elapsed : 0.0;
|
|
|
|
auto& state = pool.state();
|
|
if (state.found.load(std::memory_order_relaxed) && state.result.has_value()) {
|
|
CrackResult result{
|
|
.plaintext = *state.result,
|
|
.hash = cfg.target_hash,
|
|
.algorithm = std::string(H::name()),
|
|
.elapsed_seconds = elapsed,
|
|
.candidates_tested = tested,
|
|
.hashes_per_second = speed
|
|
};
|
|
|
|
progress.print_cracked(result);
|
|
return result;
|
|
}
|
|
|
|
progress.print_exhausted(cfg.target_hash, H::name());
|
|
return std::unexpected(CrackError::Exhausted);
|
|
}
|