Skip to content

Commit

Permalink
Merge pull request #112 from kimci86/pausable
Browse files Browse the repository at this point in the history
Pausable attack and password recovery
  • Loading branch information
kimci86 authored Jan 2, 2024
2 parents 93173bb + cb2f182 commit 6fee0a1
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 31 deletions.
8 changes: 6 additions & 2 deletions example/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ After a little while, the keys will appear!
100.0 % (13 / 13)
[17:42:44] Attack on 542303 Z values at index 6
Keys: c4490e28 b414a23d 91404b31
33.9 % (183761 / 542303)
33.9 % (183750 / 542303)
Found a solution. Stopping.
You may resume the attack with the option: --continue-attack 183750
[17:48:03] Keys
c4490e28 b414a23d 91404b31

Expand Down Expand Up @@ -151,7 +153,9 @@ Now, let us assume the password is made of 12 alpha-numerical characters.
[17:54:37] Recovering password
length 12...
Password: W4sF0rgotten
51.8 % (1993 / 3844)
51.7 % (1989 / 3844)
Found a solution. Stopping.
You may resume the password recovery with the option: --continue-recovery 573478303030
[17:54:49] Password
as bytes: 57 34 73 46 30 72 67 6f 74 74 65 6e
as text: W4sF0rgotten
Expand Down
8 changes: 8 additions & 0 deletions include/Arguments.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class Arguments
/// Tell not to use the check byte derived from ciphertext entry metadata as known plaintext
bool ignoreCheckByte = false;

/// Staring point of the attack on Z values remaining after reduction
int attackStart = 0;

/// Password from which to derive the internal password representation
std::optional<std::string> password;

Expand Down Expand Up @@ -105,6 +108,9 @@ class Arguments
/// \copydoc LengthInterval
std::optional<LengthInterval> length;

/// Starting point for password recovery
std::string recoveryStart;

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

Expand Down Expand Up @@ -137,6 +143,7 @@ class Arguments
offset,
extraPlaintext,
ignoreCheckByte,
attackStart,
password,
keys,
decipheredFile,
Expand All @@ -146,6 +153,7 @@ class Arguments
bruteforce,
length,
recoverPassword,
recoveryStart,
jobs,
exhaustive,
infoArchive,
Expand Down
4 changes: 3 additions & 1 deletion include/Attack.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ class Attack
/// \brief Iterate on Zi[2,32) candidates to try and find complete internal keys
/// \param data Data used to carry out the attack
/// \param zi_2_32_vector Zi[2,32) candidates
/// \param start Starting index of Zi[2,32) candidates in zi_2_32_vector to try.
/// Also used as an output parameter to tell where to restart.
/// \param index Index of the Zi[2,32) values relative to keystream
/// \param jobs Number of threads to use
/// \param exhaustive True to try and find all valid keys,
/// false to stop searching after the first one is found
/// \param progress Object to report progress
std::vector<Keys> attack(const Data& data, const u32vec& zi_2_32_vector, std::size_t index, int jobs, bool exhaustive, Progress& progress);
std::vector<Keys> attack(const Data& data, const u32vec& zi_2_32_vector, int& start, std::size_t index, int jobs, bool exhaustive, Progress& progress);

#endif // BKCRACK_ATTACK_HPP
25 changes: 25 additions & 0 deletions include/SigintHandler.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#ifndef BKCRACK_SIGINTHANDLER_HPP
#define BKCRACK_SIGINTHANDLER_HPP

#include "Progress.hpp"

/// \brief Utility class to set a progress state to Progress::State::Canceled when SIGINT arrives
///
/// \note There should exist at most one instance of this class at any time.
class SigintHandler
{
public:
/// Enable the signal handler
SigintHandler(std::atomic<Progress::State>& destination);

/// Disable the signal handler
~SigintHandler();

/// Deleted copy constructor
SigintHandler(const SigintHandler& other) = delete;

/// Deleted assignment operator
SigintHandler& operator=(const SigintHandler& other) = delete;
};

#endif // BKCRACK_SIGINTHANDLER_HPP
4 changes: 3 additions & 1 deletion include/password.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,15 @@ class Recovery
/// \param charset The set of characters with which to constitute password candidates
/// \param minLength The smallest password length to try
/// \param maxLength The greatest password length to try
/// \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).
std::vector<std::string> recoverPassword(const Keys& keys, const bytevec& charset, std::size_t minLength, std::size_t maxLength, int jobs, bool exhaustive, Progress& progress);
std::vector<std::string> recoverPassword(const Keys& keys, const bytevec& charset, std::size_t minLength, std::size_t maxLength, std::string& start, int jobs, bool exhaustive, Progress& progress);

#endif // BKCRACK_PASSWORD_HPP
11 changes: 11 additions & 0 deletions src/Arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ void Arguments::parseArgument()
case Option::ignoreCheckByte:
ignoreCheckByte = true;
break;
case Option::attackStart:
attackStart = readInt("checkpoint");
break;
case Option::password:
password = readString("password");
break;
Expand Down Expand Up @@ -278,6 +281,12 @@ void Arguments::parseArgument()
}, parseInterval(readString("length")));
bruteforce = readCharset();
break;
case Option::recoveryStart:
{
const bytevec checkpoint = readHex("checkpoint");
recoveryStart.assign(checkpoint.begin(), checkpoint.end());
break;
}
case Option::jobs:
jobs = readInt("count");
break;
Expand Down Expand Up @@ -317,6 +326,7 @@ Arguments::Option Arguments::readOption(const std::string& description)
PAIRS(-o, --offset, offset),
PAIRS(-x, --extra, extraPlaintext),
PAIR ( --ignore-check-byte, ignoreCheckByte),
PAIR ( --continue-attack, attackStart),
PAIR ( --password, password),
PAIRS(-k, --keys, keys),
PAIRS(-d, --decipher, decipheredFile),
Expand All @@ -326,6 +336,7 @@ Arguments::Option Arguments::readOption(const std::string& description)
PAIRS(-b, --bruteforce, bruteforce),
PAIRS(-l, --length, length),
PAIRS(-r, --recover-password, recoverPassword),
PAIR ( --continue-recovery, recoveryStart),
PAIRS(-j, --jobs, jobs),
PAIRS(-e, --exhaustive, exhaustive),
PAIRS(-L, --list, infoArchive),
Expand Down
13 changes: 9 additions & 4 deletions src/Attack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ void Attack::testXlist()
progress.state = Progress::State::EarlyExit;
}

std::vector<Keys> attack(const Data& data, const u32vec& zi_2_32_vector, std::size_t index, int jobs, const bool exhaustive, Progress& progress)
std::vector<Keys> attack(const Data& data, const u32vec& zi_2_32_vector, int& start, std::size_t index, int jobs, const bool exhaustive, Progress& progress)
{
const uint32* candidates = zi_2_32_vector.data();
const auto size = static_cast<int>(zi_2_32_vector.size());
Expand All @@ -186,23 +186,28 @@ std::vector<Keys> attack(const Data& data, const u32vec& zi_2_32_vector, std::si
std::mutex solutionsMutex;
Attack worker(data, index, solutions, solutionsMutex, exhaustive, progress);

progress.done = 0;
progress.done = start;
progress.total = size;

const auto threadCount = std::clamp(jobs, 1, size);
auto threads = std::vector<std::thread>{};
auto nextCandidateIndex = std::atomic<int>{0};
auto nextCandidateIndex = std::atomic<int>{start};
for(auto i = 0; i < threadCount; ++i)
threads.emplace_back(
[&nextCandidateIndex, size, &progress, candidates, worker]() mutable {
for(auto i = nextCandidateIndex++; i < size && progress.state == Progress::State::Normal; i = nextCandidateIndex++)
for(auto i = nextCandidateIndex++; i < size; i = nextCandidateIndex++)
{
worker.carryout(candidates[i]);
progress.done++;

if(progress.state != Progress::State::Normal)
break;
}
});
for(auto& thread : threads)
thread.join();

start = nextCandidateIndex;

return solutions;
}
29 changes: 29 additions & 0 deletions src/SigintHandler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "SigintHandler.hpp"
#include <csignal>

namespace
{

static_assert(std::atomic<Progress::State>::is_always_lock_free, "atomics must be lock-free to be signal-safe");

std::atomic<Progress::State>* destination = nullptr;

} // namespace

void bkcrackSigintHandler(int sig)
{
*destination = Progress::State::Canceled;
std::signal(sig, &bkcrackSigintHandler);
}

SigintHandler::SigintHandler(std::atomic<Progress::State>& destination)
{
::destination = &destination;
std::signal(SIGINT, &bkcrackSigintHandler);
}

SigintHandler::~SigintHandler()
{
std::signal(SIGINT, SIG_DFL);
destination = nullptr;
}
64 changes: 55 additions & 9 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "VirtualTerminalSupport.hpp"
#include "log.hpp"
#include "ConsoleProgress.hpp"
#include "SigintHandler.hpp"
#include "file.hpp"
#include "Zip.hpp"
#include "Arguments.hpp"
Expand Down Expand Up @@ -35,10 +36,14 @@ Options to get the internal password representation:
--ignore-check-byte Do not automatically use ciphertext's check byte
as known plaintext
--password <password> Password from which to derive the internal password
representation. Useful for testing purposes and
advanced scenarios such as reverting the effect of
the --change-password command.
--continue-attack <checkpoint>
Starting point of the attack. Useful to continue a previous
non-exhaustive or interrupted attack.
--password <password>
Password from which to derive the internal password representation.
Useful for testing purposes and advanced scenarios such as reverting
the effect of the --change-password command.
Options to use the internal password representation:
-k, --keys <X> <Y> <Z> Internal password representation as three 32-bits
Expand Down Expand Up @@ -78,6 +83,10 @@ Options to use the internal password representation:
-r, --recover-password [ <min>..<max> | <min>.. | ..<max> | <max> ] <charset>
Shortcut for --length and --bruteforce options
--continue-recovery <checkpoint>
Starting point of the password recovery. Useful to continue a previous
non-exhaustive or interrupted password recovery.
Other options:
-j, --jobs <count> Number of threads to use for parallelized operations
-e, --exhaustive Exhaustively look for all solutions (keys or
Expand Down Expand Up @@ -143,9 +152,23 @@ try
std::cout << "[" << put_time << "] Attack on " << zr.getCandidates().size() << " Z values at index "
<< (static_cast<int>(data.offset + zr.getIndex()) - static_cast<int>(Data::ENCRYPTION_HEADER_SIZE)) << std::endl;

const auto [state, restart] = [&]() -> std::pair<Progress::State, int>
{
int start = args.attackStart;
ConsoleProgress progress(std::cout);
keysvec = attack(data, zr.getCandidates(), zr.getIndex(), args.jobs, args.exhaustive, progress);
SigintHandler sigintHandler{progress.state};
keysvec = attack(data, zr.getCandidates(), start, zr.getIndex(), args.jobs, args.exhaustive, progress);
return {progress.state, start};
}();

if(state != Progress::State::Normal)
{
if(state == Progress::State::Canceled)
std::cout << "Operation interrupted by user." << std::endl;
else if(state == Progress::State::EarlyExit)
std::cout << "Found a solution. Stopping." << std::endl;

std::cout << "You may resume the attack with the option: --continue-attack " << restart << std::endl;
}

// print the keys
Expand Down Expand Up @@ -238,16 +261,39 @@ try
// recover password
if(args.bruteforce)
{
const auto& charset = *args.bruteforce;
const auto& [minLength, maxLength] = args.length.value_or(Arguments::LengthInterval{});

std::cout << "[" << put_time << "] Recovering password" << std::endl;

std::vector<std::string> passwords;

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{});
std::string start = args.recoveryStart;

ConsoleProgress progress(std::cout);
passwords = recoverPassword(keysvec.front(), charset, minLength, maxLength, args.jobs, args.exhaustive, progress);
SigintHandler sigintHandler(progress.state);
passwords = recoverPassword(keysvec.front(), charset, minLength, maxLength, start, args.jobs, args.exhaustive, progress);
return {progress.state, start};
}();

if(state != Progress::State::Normal)
{
if(state == Progress::State::Canceled)
std::cout << "Operation interrupted by user." << std::endl;
else if(state == Progress::State::EarlyExit)
std::cout << "Found a solution. Stopping." << std::endl;

const auto flagsBefore = std::cout.setf(std::ios::hex, std::ios::basefield);
const auto fillBefore = std::cout.fill('0');

std::cout << "You may resume the password recovery with the option: --continue-recovery ";
for(byte c : restart)
std::cout << std::setw(2) << static_cast<int>(c);
std::cout << std::endl;

std::cout.fill(fillBefore);
std::cout.flags(flagsBefore);
}

std::cout << "[" << put_time << "] ";
Expand Down
Loading

0 comments on commit 6fee0a1

Please sign in to comment.