From 3f284fae3ecfedc49b489367304adac1fe5d4813 Mon Sep 17 00:00:00 2001 From: kimci86 Date: Fri, 28 Jun 2024 00:00:00 +0200 Subject: [PATCH] Add arguments for mask-based password recovery --- include/Arguments.hpp | 11 ++++- src/Arguments.cpp | 105 +++++++++++++++++++++++++++++------------- src/main.cpp | 9 ++++ 3 files changed, 91 insertions(+), 34 deletions(-) diff --git a/include/Arguments.hpp b/include/Arguments.hpp index e7eef21..9152f94 100644 --- a/include/Arguments.hpp +++ b/include/Arguments.hpp @@ -5,9 +5,11 @@ #include "Keys.hpp" #include "types.hpp" +#include #include #include #include +#include /// Parse and store arguments class Arguments @@ -115,6 +117,9 @@ class Arguments /// Starting point for password recovery std::string recoveryStart; + /// Mask for password recovery, alternative to bruteforce and length + std::optional>> mask; + /// Number of threads to use for parallelized operations int jobs; @@ -134,6 +139,8 @@ class Arguments const char** m_current; const char** const m_end; + std::unordered_map> m_charsets; + auto finished() const -> bool; void parseArgument(); @@ -162,6 +169,8 @@ class Arguments length, recoverPassword, recoveryStart, + mask, + charset, jobs, exhaustive, infoArchive, @@ -175,7 +184,7 @@ class Arguments auto readSize(const std::string& description) -> std::size_t; auto readHex(const std::string& description) -> std::vector; auto readKey(const std::string& description) -> std::uint32_t; - auto readCharset() -> std::vector; + auto readCharset() -> std::bitset<256>; }; #endif // BKCRACK_ARGUMENTS_HPP diff --git a/src/Arguments.cpp b/src/Arguments.cpp index 0a61bf1..55ef9a6 100644 --- a/src/Arguments.cpp +++ b/src/Arguments.cpp @@ -12,7 +12,7 @@ namespace { -auto charRange(char first, char last) -> std::bitset<256> +auto charRange(unsigned char first, unsigned char last) -> std::bitset<256> { auto bitset = std::bitset<256>{}; @@ -24,6 +24,16 @@ auto charRange(char first, char last) -> std::bitset<256> return bitset; } +auto bitsetToVector(const std::bitset<256>& charset) -> std::vector +{ + auto result = std::vector{}; + for (auto c = 0; c < 256; c++) + if (charset[c]) + result.push_back(c); + + return result; +} + template auto translateIntParseError(F&& f, const std::string& value) { @@ -86,6 +96,19 @@ Arguments::Arguments(int argc, const char* argv[]) }()} , m_current{argv + 1} , m_end{argv + argc} +, m_charsets{[]() -> std::unordered_map> + { + const auto lowercase = charRange('a', 'z'); + const auto uppercase = charRange('A', 'Z'); + const auto digits = charRange('0', '9'); + const auto alphanum = lowercase | uppercase | digits; + const auto printable = charRange(' ', '~'); + const auto punctuation = printable & ~alphanum; + const auto bytes = charRange('\0', '\xff'); + + return {{'l', lowercase}, {'u', uppercase}, {'d', digits}, {'s', punctuation}, + {'a', alphanum}, {'p', printable}, {'b', bytes}, {'?', charRange('?', '?')}}; + }()} { // parse arguments while (!finished()) @@ -97,8 +120,8 @@ Arguments::Arguments(int argc, const char* argv[]) // check constraints on arguments if (keys) { - if (!decipheredFile && !decryptedArchive && !changePassword && !changeKeys && !bruteforce) - throw Error{"-d, -D, -U, --change-keys or --bruteforce parameter is missing (required by -k)"}; + if (!decipheredFile && !decryptedArchive && !changePassword && !changeKeys && !bruteforce && !mask) + throw Error{"-d, -D, -U, --change-keys, --bruteforce or -m parameter is missing (required by -k)"}; } else if (!password) { @@ -265,7 +288,7 @@ void Arguments::parseArgument() changeKeys = {readString("unlockedzip"), {readKey("X"), readKey("Y"), readKey("Z")}}; break; case Option::bruteforce: - bruteforce = readCharset(); + bruteforce = bitsetToVector(readCharset()); break; case Option::length: length = length.value_or(LengthInterval{}) & @@ -290,8 +313,45 @@ void Arguments::parseArgument() return arg; }, parseInterval(readString("length"))); - bruteforce = readCharset(); + bruteforce = bitsetToVector(readCharset()); break; + case Option::mask: + { + const auto maskArg = readString("mask"); + if (maskArg.empty()) + throw Error{"mask cannot be empty"}; + + auto& result = mask.emplace(); + for (auto it = maskArg.begin(); it != maskArg.end(); ++it) + { + if (*it == '?') + { + if (++it == maskArg.end()) + { + result.push_back({'?'}); + break; + } + + if (const auto charsetIt = m_charsets.find(*it); charsetIt != m_charsets.end()) + result.push_back(bitsetToVector(charsetIt->second)); + else + throw Error{std::string{"unknown charset ?"} + *it}; + } + else + result.push_back({static_cast(*it)}); + } + break; + } + case Option::charset: + { + const auto identifier = readString("identifier"); + if (identifier.size() != 1) + throw Error{"charset identifier must be a single character, got \"" + identifier + "\""}; + if (m_charsets.count(identifier[0])) + throw Error{"charset ?" + identifier + " is already defined, it cannot be redefined"}; + m_charsets[identifier[0]] = readCharset(); + break; + } case Option::recoveryStart: { const auto checkpoint = readHex("checkpoint"); @@ -352,6 +412,8 @@ auto Arguments::readOption(const std::string& description) -> Arguments::Option PAIRS(-b, --bruteforce, bruteforce), PAIRS(-l, --length, length), PAIRS(-r, --recover-password, recoverPassword), + PAIRS(-m, --mask, mask), + PAIRS(-s, --custom-set, charset), PAIR ( --continue-recovery, recoveryStart), PAIRS(-j, --jobs, jobs), PAIRS(-e, --exhaustive, exhaustive), @@ -409,15 +471,8 @@ auto Arguments::readKey(const std::string& description) -> std::uint32_t return static_cast(std::stoul(str, nullptr, 16)); } -auto Arguments::readCharset() -> std::vector +auto Arguments::readCharset() -> std::bitset<256> { - const auto lowercase = charRange('a', 'z'); - const auto uppercase = charRange('A', 'Z'); - const auto digits = charRange('0', '9'); - const auto alphanum = lowercase | uppercase | digits; - const auto printable = charRange(' ', '~'); - const auto punctuation = printable & ~alphanum; - const auto charsetArg = readString("charset"); if (charsetArg.empty()) throw Error{"the charset for password recovery is empty"}; @@ -434,30 +489,14 @@ auto Arguments::readCharset() -> std::vector break; } - switch (*it) - { - // clang-format off - case 'l': charset |= lowercase; break; - case 'u': charset |= uppercase; break; - case 'd': charset |= digits; break; - case 's': charset |= punctuation; break; - case 'a': charset |= alphanum; break; - case 'p': charset |= printable; break; - case 'b': charset.set(); break; - case '?': charset.set('?'); break; - // clang-format on - default: + if (const auto charsetIt = m_charsets.find(*it); charsetIt != m_charsets.end()) + charset |= charsetIt->second; + else throw Error{std::string{"unknown charset ?"} + *it}; - } } else charset.set(*it); } - auto result = std::vector{}; - for (auto c = 0; c < 256; c++) - if (charset[c]) - result.push_back(c); - - return result; + return charset; } diff --git a/src/main.cpp b/src/main.cpp index 6871415..37d80ea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -88,6 +88,15 @@ Options to use the internal password representation: -r, --recover-password [ .. | .. | .. | ] Shortcut for --length and --bruteforce options + -m, --mask + Try to recover the password or an equivalent one by generating and + testing password candidates according to the given mask. + The mask is sequence of fixed characters or character sets (predefined + or custom charsets). Example: -m ?u?l?l?l?l-?d?d?d?d + + -s, --charset + Define a custom character set. Example: -s h abcdef?d + --continue-recovery Starting point of the password recovery. Useful to continue a previous non-exhaustive or interrupted password recovery.