Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mask attack to recover password #126

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion include/Arguments.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
#include "Keys.hpp"
#include "types.hpp"

#include <bitset>
#include <limits>
#include <map>
#include <optional>
#include <unordered_map>

/// Parse and store arguments
class Arguments
Expand Down Expand Up @@ -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<std::vector<std::vector<std::uint8_t>>> mask;

/// Number of threads to use for parallelized operations
int jobs;

Expand All @@ -134,6 +139,8 @@ class Arguments
const char** m_current;
const char** const m_end;

std::unordered_map<char, std::bitset<256>> m_charsets;

auto finished() const -> bool;

void parseArgument();
Expand Down Expand Up @@ -162,6 +169,8 @@ class Arguments
length,
recoverPassword,
recoveryStart,
mask,
charset,
jobs,
exhaustive,
infoArchive,
Expand All @@ -175,7 +184,7 @@ class Arguments
auto readSize(const std::string& description) -> std::size_t;
auto readHex(const std::string& description) -> std::vector<std::uint8_t>;
auto readKey(const std::string& description) -> std::uint32_t;
auto readCharset() -> std::vector<std::uint8_t>;
auto readCharset() -> std::bitset<256>;
};

#endif // BKCRACK_ARGUMENTS_HPP
15 changes: 15 additions & 0 deletions include/password.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,19 @@ auto recoverPassword(const Keys& keys, const std::vector<std::uint8_t>& charset,
std::size_t maxLength, std::string& start, int jobs, bool exhaustive, Progress& progress)
-> std::vector<std::string>;

/// \brief Try to recover the password associated with the given keys
/// \param keys Internal keys for which a password is wanted
/// \param mask A sequence of character sets, each corresponding to the successive characters of password candidates
/// \param start Starting point in the password search space.
/// Also used as an output parameter to tell where to restart.
/// \param jobs Number of threads to use
/// \param exhaustive True to try and find all valid passwords,
/// false to stop searching after the first one is found
/// \param progress Object to report progress
/// \return A vector of passwords associated with the given keys.
/// A vector is needed instead of a single string because there can be
/// collisions (i.e. several passwords for the same keys).
auto recoverPassword(const Keys& keys, const std::vector<std::vector<std::uint8_t>>& mask, std::string& start, int jobs,
bool exhaustive, Progress& progress) -> std::vector<std::string>;

#endif // BKCRACK_PASSWORD_HPP
105 changes: 72 additions & 33 deletions src/Arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>{};

Expand All @@ -24,6 +24,16 @@ auto charRange(char first, char last) -> std::bitset<256>
return bitset;
}

auto bitsetToVector(const std::bitset<256>& charset) -> std::vector<std::uint8_t>
{
auto result = std::vector<std::uint8_t>{};
for (auto c = 0; c < 256; c++)
if (charset[c])
result.push_back(c);

return result;
}

template <typename F>
auto translateIntParseError(F&& f, const std::string& value)
{
Expand Down Expand Up @@ -86,6 +96,19 @@ Arguments::Arguments(int argc, const char* argv[])
}()}
, m_current{argv + 1}
, m_end{argv + argc}
, m_charsets{[]() -> std::unordered_map<char, 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 bytes = charRange('\0', '\xff');

return {{'l', lowercase}, {'u', uppercase}, {'d', digits}, {'s', punctuation},
{'a', alphanum}, {'p', printable}, {'b', bytes}, {'?', charRange('?', '?')}};
}()}
{
// parse arguments
while (!finished())
Expand All @@ -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)
{
Expand Down Expand Up @@ -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{}) &
Expand All @@ -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<std::uint8_t>(*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");
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -409,15 +471,8 @@ auto Arguments::readKey(const std::string& description) -> std::uint32_t
return static_cast<std::uint32_t>(std::stoul(str, nullptr, 16));
}

auto Arguments::readCharset() -> std::vector<std::uint8_t>
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"};
Expand All @@ -434,30 +489,14 @@ auto Arguments::readCharset() -> std::vector<std::uint8_t>
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<std::uint8_t>{};
for (auto c = 0; c < 256; c++)
if (charset[c])
result.push_back(c);

return result;
return charset;
}
34 changes: 26 additions & 8 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ Options to use the internal password representation:
-r, --recover-password [ <min>..<max> | <min>.. | ..<max> | <max> ] <charset>
Shortcut for --length and --bruteforce options

-m, --mask <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 <identifier> <charset>
Define a custom character set. Example: -s h abcdef?d

--continue-recovery <checkpoint>
Starting point of the password recovery. Useful to continue a previous
non-exhaustive or interrupted password recovery.
Expand Down Expand Up @@ -302,21 +311,30 @@ try
}

// recover password
if (args.bruteforce)
if (args.bruteforce || args.mask)
{
std::cout << "[" << put_time << "] Recovering password" << std::endl;

auto passwords = std::vector<std::string>{};

const auto [state, restart] = [&]() -> std::pair<Progress::State, std::string>
{
const auto& charset = *args.bruteforce;
const auto& [minLength, maxLength] = args.length.value_or(Arguments::LengthInterval{});
auto start = args.recoveryStart;
auto progress = ConsoleProgress{std::cout};
const auto sigintHandler = SigintHandler{progress.state};
passwords = recoverPassword(keysvec.front(), charset, minLength, maxLength, start, args.jobs,
args.exhaustive, progress);
auto start = args.recoveryStart;
auto progress = ConsoleProgress{std::cout};
const auto sigintHandler = SigintHandler{progress.state};

if (args.bruteforce)
{
const auto& charset = *args.bruteforce;
const auto& [minLength, maxLength] = args.length.value_or(Arguments::LengthInterval{});
passwords = recoverPassword(keysvec.front(), charset, minLength, maxLength, start, args.jobs,
args.exhaustive, progress);
}
else
{
passwords = recoverPassword(keysvec.front(), *args.mask, start, args.jobs, args.exhaustive, progress);
}

return {progress.state, start};
}();

Expand Down
Loading