244 lines
8.5 KiB
C++
244 lines
8.5 KiB
C++
/*
|
|
©AngelaMos | 2026
|
|
main.cpp
|
|
|
|
CLI entry point with hash type dispatch and attack mode selection
|
|
|
|
Parses command-line arguments via boost::program_options for hash target,
|
|
algorithm (md5/sha1/sha256/sha512/auto), attack mode (dictionary, brute-
|
|
force, or rule-based), charset selection, salt, thread count, and JSON
|
|
output. build_charset assembles a character set from comma-separated
|
|
tokens (lower, upper, digits, special). dispatch_hasher selects the
|
|
concrete EVPHasher instantiation at runtime via a switch on HashType,
|
|
then dispatch_attack picks the attack strategy (BruteForceAttack,
|
|
RuleAttack, or DictionaryAttack) based on config flags. When auto-
|
|
detection is requested, HashDetector identifies the algorithm from hex
|
|
digest length.
|
|
|
|
Key exports:
|
|
main - Entry point, returns 0 on crack success, 1 on failure or exhaustion
|
|
|
|
Connects to:
|
|
config/Config.hpp - CrackConfig, CrackResult, charset constants, defaults
|
|
core/Concepts.hpp - CrackError enum for error propagation
|
|
core/Engine.hpp - Engine::crack<H, A> template drives the crack session
|
|
hash/HashDetector.hpp - HashDetector::detect for auto-detection
|
|
hash/MD5Hasher.hpp et al. - Concrete hasher types for dispatch
|
|
attack/BruteForceAttack.hpp - BruteForceAttack for exhaustive mode
|
|
attack/DictionaryAttack.hpp - DictionaryAttack for wordlist mode
|
|
attack/RuleAttack.hpp - RuleAttack for mutation mode
|
|
*/
|
|
|
|
#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/core/Engine.hpp"
|
|
#include "src/hash/HashDetector.hpp"
|
|
#include "src/hash/MD5Hasher.hpp"
|
|
#include "src/hash/SHA1Hasher.hpp"
|
|
#include "src/hash/SHA256Hasher.hpp"
|
|
#include "src/hash/SHA512Hasher.hpp"
|
|
#include <boost/program_options.hpp>
|
|
#include <expected>
|
|
#include <iostream>
|
|
#include <print>
|
|
#include <string>
|
|
|
|
namespace po = boost::program_options;
|
|
|
|
static std::string build_charset(const std::string &spec) {
|
|
std::string result;
|
|
|
|
auto has = [&](std::string_view token) { return spec.find(token) != std::string::npos; };
|
|
|
|
if (has("lower")) {
|
|
result += config::CHARSET_LOWER;
|
|
}
|
|
if (has("upper")) {
|
|
result += config::CHARSET_UPPER;
|
|
}
|
|
if (has("digits")) {
|
|
result += config::CHARSET_DIGITS;
|
|
}
|
|
if (has("special")) {
|
|
result += config::CHARSET_SPECIAL;
|
|
}
|
|
|
|
if (result.empty()) {
|
|
result += config::CHARSET_LOWER;
|
|
result += config::CHARSET_DIGITS;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
template <Hasher H>
|
|
static auto dispatch_attack(const CrackConfig &cfg) -> std::expected<CrackResult, CrackError> {
|
|
if (cfg.bruteforce) {
|
|
return Engine::crack<H, BruteForceAttack>(cfg);
|
|
}
|
|
if (cfg.use_rules) {
|
|
return Engine::crack<H, RuleAttack>(cfg);
|
|
}
|
|
return Engine::crack<H, DictionaryAttack>(cfg);
|
|
}
|
|
|
|
static auto dispatch_hasher(HashType type, const CrackConfig &cfg)
|
|
-> std::expected<CrackResult, CrackError> {
|
|
switch (type) {
|
|
case HashType::MD5:
|
|
return dispatch_attack<MD5Hasher>(cfg);
|
|
case HashType::SHA1:
|
|
return dispatch_attack<SHA1Hasher>(cfg);
|
|
case HashType::SHA256:
|
|
return dispatch_attack<SHA256Hasher>(cfg);
|
|
case HashType::SHA512:
|
|
return dispatch_attack<SHA512Hasher>(cfg);
|
|
}
|
|
return std::unexpected(CrackError::UnsupportedAlgorithm);
|
|
}
|
|
|
|
static std::string json_escape(std::string_view s) {
|
|
std::string result;
|
|
result.reserve(s.size());
|
|
for (char c : s) {
|
|
switch (c) {
|
|
case '"':
|
|
result += "\\\"";
|
|
break;
|
|
case '\\':
|
|
result += "\\\\";
|
|
break;
|
|
case '\n':
|
|
result += "\\n";
|
|
break;
|
|
case '\r':
|
|
result += "\\r";
|
|
break;
|
|
case '\t':
|
|
result += "\\t";
|
|
break;
|
|
default:
|
|
result += c;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void write_json_result(const std::string &path, const CrackResult &result) {
|
|
auto *f = std::fopen(path.c_str(), "w");
|
|
if (!f) {
|
|
return;
|
|
}
|
|
|
|
std::fprintf(f,
|
|
"{\n"
|
|
" \"plaintext\": \"%s\",\n"
|
|
" \"hash\": \"%s\",\n"
|
|
" \"algorithm\": \"%s\",\n"
|
|
" \"elapsed_seconds\": %.4f,\n"
|
|
" \"candidates_tested\": %zu,\n"
|
|
" \"hashes_per_second\": %.2f\n"
|
|
"}\n",
|
|
json_escape(result.plaintext).c_str(), json_escape(result.hash).c_str(),
|
|
json_escape(result.algorithm).c_str(), result.elapsed_seconds,
|
|
result.candidates_tested, result.hashes_per_second);
|
|
|
|
std::fclose(f);
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
po::options_description desc("hashcracker - Multi-threaded hash cracking tool");
|
|
desc.add_options()("help,h", "Show help message")("hash", po::value<std::string>(),
|
|
"Target hash to crack")(
|
|
"type", po::value<std::string>()->default_value("auto"),
|
|
"Hash type: md5, sha1, sha256, sha512, auto")("wordlist,w", po::value<std::string>(),
|
|
"Path to wordlist file")(
|
|
"bruteforce,b", "Use brute-force attack mode")(
|
|
"charset", po::value<std::string>()->default_value("lower,digits"),
|
|
"Character sets: lower,upper,digits,special")(
|
|
"max-length", po::value<std::size_t>()->default_value(config::DEFAULT_MAX_BRUTE_LENGTH),
|
|
"Max password length for brute-force")("rules,r",
|
|
"Apply mutation rules to dictionary words")(
|
|
"chain-rules", "Chain mutation rules in combination")("salt", po::value<std::string>(),
|
|
"Salt value to prepend/append")(
|
|
"salt-position", po::value<std::string>()->default_value("prepend"),
|
|
"Salt position: prepend or append")(
|
|
"threads,t", po::value<unsigned>()->default_value(config::DEFAULT_THREAD_COUNT),
|
|
"Thread count (0 = auto)")("output,o", po::value<std::string>(),
|
|
"Write JSON result to file");
|
|
|
|
po::variables_map vm;
|
|
try {
|
|
po::store(po::parse_command_line(argc, argv, desc), vm);
|
|
po::notify(vm);
|
|
} catch (const po::error &e) {
|
|
std::println(stderr, "Error: {}", e.what());
|
|
return 1;
|
|
}
|
|
|
|
if (vm.count("help") || !vm.count("hash")) {
|
|
std::cout << desc << std::endl;
|
|
return vm.count("help") ? 0 : 1;
|
|
}
|
|
|
|
CrackConfig cfg;
|
|
cfg.target_hash = vm["hash"].as<std::string>();
|
|
cfg.hash_type = vm["type"].as<std::string>();
|
|
cfg.thread_count = vm["threads"].as<unsigned>();
|
|
cfg.bruteforce = vm.count("bruteforce") > 0;
|
|
cfg.use_rules = vm.count("rules") > 0;
|
|
cfg.chain_rules = vm.count("chain-rules") > 0;
|
|
cfg.max_length = vm["max-length"].as<std::size_t>();
|
|
|
|
if (vm.count("wordlist")) {
|
|
cfg.wordlist_path = vm["wordlist"].as<std::string>();
|
|
}
|
|
if (vm.count("salt")) {
|
|
cfg.salt = vm["salt"].as<std::string>();
|
|
}
|
|
cfg.salt_position = vm["salt-position"].as<std::string>();
|
|
if (vm.count("output")) {
|
|
cfg.output_path = vm["output"].as<std::string>();
|
|
}
|
|
|
|
if (cfg.bruteforce) {
|
|
cfg.charset = build_charset(vm["charset"].as<std::string>());
|
|
} else if (cfg.wordlist_path.empty()) {
|
|
std::println(stderr, "Error: --wordlist required for dictionary/rule attacks");
|
|
return 1;
|
|
}
|
|
|
|
HashType hash_type;
|
|
if (cfg.hash_type == "auto") {
|
|
auto detected = HashDetector::detect(cfg.target_hash);
|
|
if (!detected.has_value()) {
|
|
std::println(stderr, "Error: {}", crack_error_message(detected.error()));
|
|
return 1;
|
|
}
|
|
hash_type = *detected;
|
|
} else if (cfg.hash_type == "md5") {
|
|
hash_type = HashType::MD5;
|
|
} else if (cfg.hash_type == "sha1") {
|
|
hash_type = HashType::SHA1;
|
|
} else if (cfg.hash_type == "sha256") {
|
|
hash_type = HashType::SHA256;
|
|
} else if (cfg.hash_type == "sha512") {
|
|
hash_type = HashType::SHA512;
|
|
} else {
|
|
std::println(stderr, "Error: Unknown hash type '{}'", cfg.hash_type);
|
|
return 1;
|
|
}
|
|
|
|
auto result = dispatch_hasher(hash_type, cfg);
|
|
|
|
if (result.has_value() && !cfg.output_path.empty()) {
|
|
write_json_result(cfg.output_path, *result);
|
|
}
|
|
|
|
return result.has_value() ? 0 : 1;
|
|
}
|