from datetime import datetime, timedelta
from tempfile import gettempdir
from typing import AsyncIterable, List, Optional
from yapapi import Golem, Task, WorkContext
from yapapi.events import CommandExecuted
from yapapi.payload import vm
from yapapi.rest.activity import CommandExecutionError
examples_dir = Path(__file__).resolve().parent.parent
sys.path.append(str(examples_dir))
HASHCAT_ATTACK_MODE = 3 # stands for mask attack, hashcat -a option
KEYSPACE_OUTPUT_PATH = Path("/golem/output/keyspace")
# Ideally, this value should depend on the chunk size
MASK_ATTACK_TIMEOUT: timedelta = timedelta(minutes=30)
KEYSPACE_TIMEOUT: timedelta = timedelta(minutes=10)
arg_parser = build_parser("Run a hashcat attack (mask mode) on Golem network.")
"Example invocation: ./yacat.py --mask '?a?a?a' --hash '$P$5ZDzPE45CLLhEx/72qt3NehVzwN2Ry/'"
arg_parser.add_argument("--hash", type=str, help="Target hash to be cracked", required=True)
help="Hashcat mask to be used for the attack. Example: a value of '?a?a?a' will "
"try all 3-character combinations, where each character is mixalpha-numeric "
"(lower and upper-case letters + digits) or a special character",
"--chunk-size", # affects skip and limit hashcat parameters
help="Limit for the number of words to be checked as part of a single activity",
help="Type of hashing algorithm to use (hashcat -m option). Default: 400 (phpass)",
help="The maximum number of nodes we want to perform the attack on (default is dynamic)",
# Container object for parsed arguments
args = argparse.Namespace()
async def compute_keyspace(context: WorkContext, tasks: AsyncIterable[Task]):
"""Worker script which computes the size of the keyspace for the mask attack.
This function is used as the `worker` parameter to `Golem#execute_tasks`.
It represents a sequence of commands to be executed on a remote provider node.
cmd = f"hashcat --keyspace " f"-a {HASHCAT_ATTACK_MODE} -m {args.hash_type} {args.mask}"
s = context.new_script(timeout=KEYSPACE_TIMEOUT)
s.run("/bin/bash", "-c", cmd)
# each item is the result of a single command on the provider (including setup commands)
result: List[CommandExecuted] = await future_result
# we take the last item since it's the last command that was executed on the provider
cmd_result: CommandExecuted = result[-1]
keyspace = int(cmd_result.stdout)
task.accept_result(result=keyspace)
except CommandExecutionError as e:
raise RuntimeError(f"Failed to compute attack keyspace: {e}")
async def perform_mask_attack(ctx: WorkContext, tasks: AsyncIterable[Task]):
"""Worker script which performs a hashcat mask attack against a target hash.
This function is used as the `worker` parameter to `Golem#execute_tasks`.
It represents a sequence of commands to be executed on a remote provider node.
limit = skip + args.chunk_size
output_name = f"yacat_{skip}.potfile"
worker_output_path = f"/golem/output/{output_name}"
script = ctx.new_script(timeout=MASK_ATTACK_TIMEOUT)
script.run(f"/bin/sh", "-c", _make_attack_command(skip, limit, worker_output_path))
output_file = Path(gettempdir()) / output_name
script.download_file(worker_output_path, str(output_file))
with output_file.open() as f:
task.accept_result(result)
def _make_attack_command(skip: int, limit: int, output_path: str) -> str:
f"hashcat -a {HASHCAT_ATTACK_MODE} -m {args.hash_type} "
f"--self-test-disable --potfile-disable "
f"--skip={skip} --limit={limit} -o {output_path} "
f"'{args.hash}' '{args.mask}' || true"
def _parse_result(potfile_line: str) -> Optional[str]:
"""Helper function which parses a single .potfile line and returns the password part.
Hashcat uses its .potfile format to report results. In this format, each line consists of the
hash and its matching word, separated with a colon (e.g. `asdf1234:password`).
return potfile_line.split(":")[-1].strip()
image_hash="055911c811e56da4d75ffc928361a78ed13077933ffa8320fb1ec2db",
subnet_tag=args.subnet_tag,
payment_driver=args.payment_driver,
payment_network=args.payment_network,
start_time = datetime.now()
completed = golem.execute_tasks(
[Task(data="compute_keyspace")],
timeout=KEYSPACE_TIMEOUT,
async for task in completed:
f"Task computed: keyspace size count. The keyspace size is {keyspace}"
data = [Task(data=c) for c in range(0, keyspace, args.chunk_size)]
max_workers = args.max_workers or math.ceil(keyspace / args.chunk_size) // 2
completed = golem.execute_tasks(
timeout=MASK_ATTACK_TIMEOUT,
async for task in completed:
f"{TEXT_COLOR_CYAN}Task computed: {task}, result: {task.result}{TEXT_COLOR_DEFAULT}"
result = _parse_result(task.result)
print(f"{TEXT_COLOR_GREEN}Password found: {password}{TEXT_COLOR_DEFAULT}")
print(f"{TEXT_COLOR_RED}No password found{TEXT_COLOR_DEFAULT}")
print(f"{TEXT_COLOR_CYAN}Total time: {datetime.now() - start_time}{TEXT_COLOR_DEFAULT}")
if __name__ == "__main__":
args = arg_parser.parse_args()
run_golem_example(main(args), log_file=args.log_file)