From a0a39e275341011703d372ac173a72e77001675b Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Fri, 21 May 2021 14:59:07 +0200 Subject: [PATCH 01/16] Make yacat use single instance of Golem --- examples/yacat/yacat.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index 59200b6c9..331775640 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -4,7 +4,7 @@ import pathlib import sys -from yapapi import Executor, NoPaymentAccountError, Task, WorkContext, windows_event_loop_fix +from yapapi import Golem, NoPaymentAccountError, Task, WorkContext, windows_event_loop_fix from yapapi.log import enable_default_logger, log_summary, log_event_repr # noqa from yapapi.payload import vm @@ -94,29 +94,32 @@ async def worker_find_password(ctx: WorkContext, tasks): # By passing `event_consumer=log_summary()` we enable summary logging. # See the documentation of the `yapapi.log` module on how to set # the level of detail and format of the logged information. - async with Executor( - package=package, - max_workers=args.number_of_providers, + async with Golem( budget=10.0, - # timeout should be keyspace / number of providers dependent - timeout=timedelta(minutes=30), subnet_tag=args.subnet_tag, driver=args.driver, network=args.network, event_consumer=log_summary(log_event_repr), - ) as executor: + ) as golem: print( f"Using subnet: {TEXT_COLOR_YELLOW}{args.subnet_tag}{TEXT_COLOR_DEFAULT}, " - f"payment driver: {TEXT_COLOR_YELLOW}{executor.driver}{TEXT_COLOR_DEFAULT}, " - f"and network: {TEXT_COLOR_YELLOW}{executor.network}{TEXT_COLOR_DEFAULT}\n" + f"payment driver: {TEXT_COLOR_YELLOW}{golem.driver}{TEXT_COLOR_DEFAULT}, " + f"and network: {TEXT_COLOR_YELLOW}{golem.network}{TEXT_COLOR_DEFAULT}\n" ) keyspace_computed = False start_time = datetime.now() - # This is not a typical use of executor.submit as there is only one task, with no data: - async for _task in executor.submit(worker_check_keyspace, [Task(data=None)]): + completed = golem.execute_tasks( + worker_check_keyspace, + [Task(data="check_keyspace")], + payload=package, + max_workers=args.number_of_providers, + timeout=timedelta(minutes=30), + ) + + async for task in completed: keyspace_computed = True if not keyspace_computed: @@ -135,9 +138,15 @@ async def worker_find_password(ctx: WorkContext, tasks): ranges = range(0, keyspace, step) - async for task in executor.submit( - worker_find_password, [Task(data=range) for range in ranges] - ): + completed = golem.execute_tasks( + worker_find_password, + [Task(data=range) for range in ranges], + payload=package, + max_workers=args.number_of_providers, + timeout=timedelta(minutes=30), + ) + + async for task in completed: print( f"{TEXT_COLOR_CYAN}Task computed: {task}, result: {task.result}{TEXT_COLOR_DEFAULT}" ) From d6c8ee4e3b29d2f7a18c72ece4a9c683cff0a2c1 Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Fri, 21 May 2021 18:13:10 +0200 Subject: [PATCH 02/16] Change CLI arguments in yacat example --- examples/yacat/yacat.py | 68 ++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index 331775640..63e73d7c0 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import argparse import asyncio from datetime import datetime, timedelta import pathlib @@ -20,6 +21,27 @@ TEXT_COLOR_YELLOW, ) +HASHCAT_ATTACK_MODE = 3 # stands for mask attack, hashcat -a option + +arg_parser = build_parser("Run a hashcat attack (mask mode) on Golem network") +arg_parser.add_argument("--hash", type=str, help="Target hash to be cracked") +arg_parser.add_argument("--mask", type=str, help="Hashcat mask to be used for the attack") +arg_parser.add_argument( + "--chunk-size", # affects skip and limit hashcat parameters + type=int, + help="Limit for the number of words to be checked as part of a single activity", + default=2048, +) +arg_parser.add_argument( + "--hash-type", + type=int, + help="Type of hashing algorithm to use (hashcat -m option)", + default=400, +) + +# Container object for parsed arguments +args = argparse.Namespace() + def write_hash(hash): with open("in.hash", "w") as f: @@ -36,19 +58,6 @@ def read_keyspace(): return int(f.readline()) -def read_password(ranges): - for r in ranges: - path = pathlib.Path(f"hashcat_{r}.potfile") - if not path.is_file(): - continue - with open(path, "r") as f: - line = f.readline() - split_list = line.split(":") - if len(split_list) >= 2: - return split_list[1] - return None - - async def main(args): package = await vm.repo( image_hash="2c17589f1651baff9b82aa431850e296455777be265c2c5446c902e9", @@ -71,7 +80,7 @@ async def worker_find_password(ctx: WorkContext, tasks): async for task in tasks: skip = task.data - limit = skip + step + limit = skip + args.chunk_size # Commands to be run on the provider commands = ( @@ -113,9 +122,9 @@ async def worker_find_password(ctx: WorkContext, tasks): completed = golem.execute_tasks( worker_check_keyspace, - [Task(data="check_keyspace")], + [Task(data="calculate_keyspace")], payload=package, - max_workers=args.number_of_providers, + max_workers=1, timeout=timedelta(minutes=30), ) @@ -134,15 +143,14 @@ async def worker_find_password(ctx: WorkContext, tasks): f"{TEXT_COLOR_DEFAULT}" ) - step = int(keyspace / args.number_of_providers) + 1 - - ranges = range(0, keyspace, step) + data = [Task(data=c) for c in range(0, keyspace, args.chunk_size)] + max_workers = keyspace // args.chunk_size completed = golem.execute_tasks( worker_find_password, - [Task(data=range) for range in ranges], + data, payload=package, - max_workers=args.number_of_providers, + max_workers=max_workers, timeout=timedelta(minutes=30), ) @@ -151,24 +159,16 @@ async def worker_find_password(ctx: WorkContext, tasks): f"{TEXT_COLOR_CYAN}Task computed: {task}, result: {task.result}{TEXT_COLOR_DEFAULT}" ) - password = read_password(ranges) - - if password is None: - print(f"{TEXT_COLOR_RED}No password found{TEXT_COLOR_DEFAULT}") - else: - print(f"{TEXT_COLOR_GREEN}Password found: {password}{TEXT_COLOR_DEFAULT}") + # if password is None: + # print(f"{TEXT_COLOR_RED}No password found{TEXT_COLOR_DEFAULT}") + # else: + # print(f"{TEXT_COLOR_GREEN}Password found: {password}{TEXT_COLOR_DEFAULT}") print(f"{TEXT_COLOR_CYAN}Total time: {datetime.now() - start_time}{TEXT_COLOR_DEFAULT}") if __name__ == "__main__": - parser = build_parser("yacat") - - parser.add_argument("--number-of-providers", dest="number_of_providers", type=int, default=3) - parser.add_argument("mask") - parser.add_argument("hash") - - args = parser.parse_args() + args = arg_parser.parse_args() # This is only required when running on Windows with Python prior to 3.8: windows_event_loop_fix() From 54b92ff9799a5431693930e47e4124c85da52555 Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Tue, 25 May 2021 10:32:02 +0200 Subject: [PATCH 03/16] Refactor keyspace and attack worker functions --- examples/yacat/yacat.py | 116 ++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index 63e73d7c0..83151a1a7 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -2,14 +2,17 @@ import argparse import asyncio from datetime import datetime, timedelta -import pathlib +from pathlib import Path import sys +from tempfile import NamedTemporaryFile +from typing import AsyncIterable from yapapi import Golem, NoPaymentAccountError, Task, WorkContext, windows_event_loop_fix +from yapapi.executor.events import CommandExecuted from yapapi.log import enable_default_logger, log_summary, log_event_repr # noqa from yapapi.payload import vm -examples_dir = pathlib.Path(__file__).resolve().parent.parent +examples_dir = Path(__file__).resolve().parent.parent sys.path.append(str(examples_dir)) from utils import ( @@ -22,6 +25,7 @@ ) HASHCAT_ATTACK_MODE = 3 # stands for mask attack, hashcat -a option +KEYSPACE_OUTPUT_PATH = Path("/golem/output/keyspace") arg_parser = build_parser("Run a hashcat attack (mask mode) on Golem network") arg_parser.add_argument("--hash", type=str, help="Target hash to be cracked") @@ -43,19 +47,59 @@ args = argparse.Namespace() -def write_hash(hash): - with open("in.hash", "w") as f: - f.write(hash) +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. + """ + async for task in tasks: + cmd = f"hashcat --keyspace " f"-a {HASHCAT_ATTACK_MODE} -m {args.hash_type} {args.mask}" + context.run("/bin/bash", "-c", cmd) -def write_keyspace_check_script(mask): - with open("keyspace.sh", "w") as f: - f.write(f"hashcat --keyspace -a 3 {mask} -m 400 > /golem/work/keyspace.txt") + future_result = yield context.commit(timeout=timedelta(minutes=10)) + # 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] + if cmd_result.success: + keyspace = int(cmd_result.stdout) + else: + raise RuntimeError("Failed to compute attack keyspace") -def read_keyspace(): - with open("keyspace.txt", "r") as f: - return int(f.readline()) + task.accept_result(result=keyspace) + + +async def perform_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. + """ + async for task in tasks: + skip = task.data + limit = skip + args.chunk_size + worker_output_path = f"/golem/output/hashcat_{skip}.potfile" + + ctx.run(f"/bin/sh", "-c", _make_attack_command(skip, limit, worker_output_path)) + + output_file = NamedTemporaryFile() + ctx.download_file(worker_output_path, output_file.name) + # ExeOptions? + yield ctx.commit(timeout=timedelta(minutes=10)) + task.accept_result(result=output_file.file.readline()) + output_file.close() + + +def _make_attack_command(skip: int, limit: int, output_path: str) -> str: + return ( + f"touch {output_path}; " + 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" # TODO this may cover up other errors + ) async def main(args): @@ -65,41 +109,6 @@ async def main(args): min_storage_gib=2.0, ) - async def worker_check_keyspace(ctx: WorkContext, tasks): - async for task in tasks: - keyspace_sh_filename = "keyspace.sh" - ctx.send_file(keyspace_sh_filename, "/golem/work/keyspace.sh") - ctx.run("/bin/sh", "/golem/work/keyspace.sh") - output_file = "keyspace.txt" - ctx.download_file("/golem/work/keyspace.txt", output_file) - yield ctx.commit(timeout=timedelta(minutes=10)) - task.accept_result() - - async def worker_find_password(ctx: WorkContext, tasks): - ctx.send_file("in.hash", "/golem/work/in.hash") - - async for task in tasks: - skip = task.data - limit = skip + args.chunk_size - - # Commands to be run on the provider - commands = ( - "rm -f /golem/work/*.potfile ~/.hashcat/hashcat.potfile; " - f"touch /golem/work/hashcat_{skip}.potfile; " - f"hashcat -a 3 -m 400 /golem/work/in.hash {args.mask} --skip={skip} --limit={limit} --self-test-disable -o /golem/work/hashcat_{skip}.potfile || true" - ) - ctx.run(f"/bin/sh", "-c", commands) - - output_file = f"hashcat_{skip}.potfile" - ctx.download_file(f"/golem/work/hashcat_{skip}.potfile", output_file) - yield ctx.commit(timeout=timedelta(minutes=10)) - task.accept_result(result=output_file) - - # beginning of the main flow - - write_hash(args.hash) - write_keyspace_check_script(args.mask) - # By passing `event_consumer=log_summary()` we enable summary logging. # See the documentation of the `yapapi.log` module on how to set # the level of detail and format of the logged information. @@ -117,25 +126,18 @@ async def worker_find_password(ctx: WorkContext, tasks): f"and network: {TEXT_COLOR_YELLOW}{golem.network}{TEXT_COLOR_DEFAULT}\n" ) - keyspace_computed = False start_time = datetime.now() completed = golem.execute_tasks( - worker_check_keyspace, - [Task(data="calculate_keyspace")], + compute_keyspace, + [Task(data="compute_keyspace")], payload=package, max_workers=1, timeout=timedelta(minutes=30), ) async for task in completed: - keyspace_computed = True - - if not keyspace_computed: - # Assume the errors have been already reported and we may return quietly. - return - - keyspace = read_keyspace() + keyspace = task.result print( f"{TEXT_COLOR_CYAN}" @@ -147,7 +149,7 @@ async def worker_find_password(ctx: WorkContext, tasks): max_workers = keyspace // args.chunk_size completed = golem.execute_tasks( - worker_find_password, + perform_attack, data, payload=package, max_workers=max_workers, From c60e2971b8571e8757f6b83f89e8c1a4a27b4583 Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Tue, 25 May 2021 10:45:30 +0200 Subject: [PATCH 04/16] Update Dockerfile and VM image --- examples/yacat/yacat.Dockerfile | 54 ++------------------------------- examples/yacat/yacat.py | 2 +- 2 files changed, 4 insertions(+), 52 deletions(-) diff --git a/examples/yacat/yacat.Dockerfile b/examples/yacat/yacat.Dockerfile index b48df19c5..121124498 100644 --- a/examples/yacat/yacat.Dockerfile +++ b/examples/yacat/yacat.Dockerfile @@ -1,52 +1,4 @@ -FROM golemfactory/base:1.5 +FROM dizcza/docker-hashcat:intel-cpu -MAINTAINER Radek Tereszczuk - - -RUN apt-get update && apt-get install -y alien clinfo - -# Install Intel OpenCL driver - -#ENV INTEL_OPENCL_URL=http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/13793/l_opencl_p_18.1.0.013.tgz -ENV INTEL_OPENCL_URL=http://registrationcenter-download.intel.com/akdlm/irc_nas/9019/opencl_runtime_16.1.1_x64_ubuntu_6.4.0.25.tgz - -RUN mkdir -p /tmp/opencl-driver-intel -WORKDIR /tmp/opencl-driver-intel -RUN curl -O $INTEL_OPENCL_URL; \ - tar -xzf $(basename $INTEL_OPENCL_URL); \ - for i in $(basename $INTEL_OPENCL_URL .tgz)/rpm/*.rpm; do alien --to-deb $i; done; \ - dpkg -i *.deb; \ - mkdir -p /etc/OpenCL/vendors; \ - echo /opt/intel/*/lib64/libintelocl.so > /etc/OpenCL/vendors/intel.icd; \ - rm -rf * - -ENV HASHCAT_VERSION hashcat-5.1.0 -ENV HASHCAT_UTILS_VERSION 1.9 - -# Update & install packages for installing hashcat -RUN apt-get update && \ - apt-get install -y wget p7zip make build-essential git libcurl4-openssl-dev libssl-dev zlib1g-dev - -RUN mkdir /golem/yacat - -WORKDIR /golem/yacat -RUN wget --no-check-certificate https://hashcat.net/files/${HASHCAT_VERSION}.7z && \ - 7zr x ${HASHCAT_VERSION}.7z && \ - rm ${HASHCAT_VERSION}.7z - -RUN wget --no-check-certificate https://github.com/hashcat/hashcat-utils/releases/download/v${HASHCAT_UTILS_VERSION}/hashcat-utils-${HASHCAT_UTILS_VERSION}.7z && \ - 7zr x hashcat-utils-${HASHCAT_UTILS_VERSION}.7z && \ - rm hashcat-utils-${HASHCAT_UTILS_VERSION}.7z - -#Add link for binary -RUN ln -s /golem/yacat/${HASHCAT_VERSION}/hashcat64.bin /usr/bin/hashcat -RUN ln -s /golem/yacat/hashcat-utils-${HASHCAT_UTILS_VERSION}/bin/cap2hccapx.bin /usr/bin/cap2hccapx - -RUN cp /golem/yacat/${HASHCAT_VERSION}/hashcat.hcstat2 /golem/yacat -RUN chmod -R 777 /golem/yacat - -RUN apt clean - -WORKDIR /golem/work - -VOLUME /golem/work /golem/output /golem/resource +VOLUME /golem/input /golem/output +WORKDIR /golem/entrypoint diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index 83151a1a7..911edb7d7 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -104,7 +104,7 @@ def _make_attack_command(skip: int, limit: int, output_path: str) -> str: async def main(args): package = await vm.repo( - image_hash="2c17589f1651baff9b82aa431850e296455777be265c2c5446c902e9", + image_hash="055911c811e56da4d75ffc928361a78ed13077933ffa8320fb1ec2db", min_mem_gib=0.5, min_storage_gib=2.0, ) From e55c23901f09ada552da9fecaa5fad5f02920855 Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Wed, 26 May 2021 09:20:21 +0200 Subject: [PATCH 05/16] Add parsing and printing the result --- examples/yacat/yacat.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index 911edb7d7..f46eca441 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -5,7 +5,7 @@ from pathlib import Path import sys from tempfile import NamedTemporaryFile -from typing import AsyncIterable +from typing import AsyncIterable, Optional from yapapi import Golem, NoPaymentAccountError, Task, WorkContext, windows_event_loop_fix from yapapi.executor.events import CommandExecuted @@ -83,12 +83,13 @@ async def perform_attack(ctx: WorkContext, tasks: AsyncIterable[Task]): worker_output_path = f"/golem/output/hashcat_{skip}.potfile" ctx.run(f"/bin/sh", "-c", _make_attack_command(skip, limit, worker_output_path)) - output_file = NamedTemporaryFile() ctx.download_file(worker_output_path, output_file.name) - # ExeOptions? + yield ctx.commit(timeout=timedelta(minutes=10)) - task.accept_result(result=output_file.file.readline()) + + result = output_file.file.readline() + task.accept_result(result) output_file.close() @@ -98,10 +99,22 @@ 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" # TODO this may cover up other errors + f"'{args.hash}' '{args.mask}' || true" ) +def _parse_result(potfile_line: bytes) -> 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`). + """ + potfile_line = potfile_line.decode("utf-8") + if potfile_line: + return potfile_line.split(":")[-1].strip() + return None + + async def main(args): package = await vm.repo( image_hash="055911c811e56da4d75ffc928361a78ed13077933ffa8320fb1ec2db", @@ -156,15 +169,21 @@ async def main(args): timeout=timedelta(minutes=30), ) + password = None + async for task in completed: print( f"{TEXT_COLOR_CYAN}Task computed: {task}, result: {task.result}{TEXT_COLOR_DEFAULT}" ) - # if password is None: - # print(f"{TEXT_COLOR_RED}No password found{TEXT_COLOR_DEFAULT}") - # else: - # print(f"{TEXT_COLOR_GREEN}Password found: {password}{TEXT_COLOR_DEFAULT}") + result = _parse_result(task.result) + if result: + password = result + + if password: + print(f"{TEXT_COLOR_GREEN}Password found: {password}{TEXT_COLOR_DEFAULT}") + else: + 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}") From edca64c9518acdcd84568e853b2c6485b9ca0d7a Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Wed, 26 May 2021 09:29:59 +0200 Subject: [PATCH 06/16] Add default timeouts --- examples/yacat/yacat.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index f46eca441..f4f3109f2 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -27,6 +27,10 @@ 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 +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") arg_parser.add_argument("--hash", type=str, help="Target hash to be cracked") arg_parser.add_argument("--mask", type=str, help="Hashcat mask to be used for the attack") @@ -57,7 +61,7 @@ async def compute_keyspace(context: WorkContext, tasks: AsyncIterable[Task]): cmd = f"hashcat --keyspace " f"-a {HASHCAT_ATTACK_MODE} -m {args.hash_type} {args.mask}" context.run("/bin/bash", "-c", cmd) - future_result = yield context.commit(timeout=timedelta(minutes=10)) + future_result = yield context.commit(timeout=KEYSPACE_TIMEOUT) # 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 @@ -86,7 +90,7 @@ async def perform_attack(ctx: WorkContext, tasks: AsyncIterable[Task]): output_file = NamedTemporaryFile() ctx.download_file(worker_output_path, output_file.name) - yield ctx.commit(timeout=timedelta(minutes=10)) + yield ctx.commit(timeout=ATTACK_TIMEOUT) result = output_file.file.readline() task.accept_result(result) @@ -146,7 +150,7 @@ async def main(args): [Task(data="compute_keyspace")], payload=package, max_workers=1, - timeout=timedelta(minutes=30), + timeout=KEYSPACE_TIMEOUT, ) async for task in completed: @@ -166,7 +170,7 @@ async def main(args): data, payload=package, max_workers=max_workers, - timeout=timedelta(minutes=30), + timeout=ATTACK_TIMEOUT, ) password = None From c859c09ed985fec8b1f22eb6f66ed4f764fc764f Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Wed, 26 May 2021 09:40:26 +0200 Subject: [PATCH 07/16] Add max_workers CLI argument --- examples/yacat/yacat.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index f4f3109f2..e50a6ed28 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -46,6 +46,12 @@ help="Type of hashing algorithm to use (hashcat -m option)", default=400, ) +arg_parser.add_argument( + "--max-workers", + type=int, + help="The maximum number of nodes we want to perform the attack on (default is dynamic)", + default=None, +) # Container object for parsed arguments args = argparse.Namespace() @@ -163,7 +169,7 @@ async def main(args): ) data = [Task(data=c) for c in range(0, keyspace, args.chunk_size)] - max_workers = keyspace // args.chunk_size + max_workers = args.max_workers or (keyspace // args.chunk_size) // 2 completed = golem.execute_tasks( perform_attack, From 70ff8c0c9d6cdcc7a762c1821a7c9deb1cd349f5 Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Wed, 26 May 2021 10:02:27 +0200 Subject: [PATCH 08/16] Add default log file to examples utils --- examples/utils.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/utils.py b/examples/utils.py index 99cce5133..c7725b8c8 100644 --- a/examples/utils.py +++ b/examples/utils.py @@ -1,5 +1,9 @@ """Utilities for yapapi example scripts.""" import argparse +from datetime import datetime, timezone +from pathlib import Path +import tempfile + import colorama # type: ignore @@ -18,6 +22,9 @@ def build_parser(description: str): + current_time_str = datetime.now(tz=timezone.utc).strftime("%Y%m%d_%H%M%S%z") + default_log_path = Path(tempfile.gettempdir()) / f"yapapi_{current_time_str}.log" + parser = argparse.ArgumentParser(description=description) parser.add_argument("--driver", help="Payment driver name, for example `zksync`") parser.add_argument("--network", help="Network name, for example `rinkeby`") @@ -25,6 +32,8 @@ def build_parser(description: str): "--subnet-tag", default="devnet-beta.1", help="Subnet name; default: %(default)s" ) parser.add_argument( - "--log-file", default=None, help="Log file for YAPAPI; default: %(default)s" + "--log-file", + default=str(default_log_path), + help="Log file for YAPAPI; default: %(default)s", ) return parser From 8f50f3a356eecdb2d34b4d9549f0890e069b5538 Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Wed, 26 May 2021 10:10:38 +0200 Subject: [PATCH 09/16] Update default chunk_size value --- examples/yacat/yacat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index e50a6ed28..6b1f0d923 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -38,7 +38,7 @@ "--chunk-size", # affects skip and limit hashcat parameters type=int, help="Limit for the number of words to be checked as part of a single activity", - default=2048, + default=4096, ) arg_parser.add_argument( "--hash-type", From 4e1386dc627f95e9cfac4e0187b6ff9c4abb1822 Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Wed, 26 May 2021 10:15:52 +0200 Subject: [PATCH 10/16] Make hash and mask arguments required --- examples/yacat/yacat.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index 6b1f0d923..8476c12ce 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -32,8 +32,10 @@ KEYSPACE_TIMEOUT: timedelta = timedelta(minutes=10) arg_parser = build_parser("Run a hashcat attack (mask mode) on Golem network") -arg_parser.add_argument("--hash", type=str, help="Target hash to be cracked") -arg_parser.add_argument("--mask", type=str, help="Hashcat mask to be used for the attack") +arg_parser.add_argument("--hash", type=str, help="Target hash to be cracked", required=True) +arg_parser.add_argument( + "--mask", type=str, help="Hashcat mask to be used for the attack", required=True +) arg_parser.add_argument( "--chunk-size", # affects skip and limit hashcat parameters type=int, From daad9bc599df209aeff3806cbb741470e102d77e Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Fri, 28 May 2021 13:42:00 +0200 Subject: [PATCH 11/16] Rename mask attack function and constant --- examples/yacat/yacat.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index 8476c12ce..d59750e5e 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -28,7 +28,7 @@ KEYSPACE_OUTPUT_PATH = Path("/golem/output/keyspace") # Ideally, this value should depend on the chunk size -ATTACK_TIMEOUT: timedelta = timedelta(minutes=30) +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") @@ -83,7 +83,7 @@ async def compute_keyspace(context: WorkContext, tasks: AsyncIterable[Task]): task.accept_result(result=keyspace) -async def perform_attack(ctx: WorkContext, tasks: AsyncIterable[Task]): +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`. @@ -98,7 +98,7 @@ async def perform_attack(ctx: WorkContext, tasks: AsyncIterable[Task]): output_file = NamedTemporaryFile() ctx.download_file(worker_output_path, output_file.name) - yield ctx.commit(timeout=ATTACK_TIMEOUT) + yield ctx.commit(timeout=MASK_ATTACK_TIMEOUT) result = output_file.file.readline() task.accept_result(result) @@ -174,11 +174,11 @@ async def main(args): max_workers = args.max_workers or (keyspace // args.chunk_size) // 2 completed = golem.execute_tasks( - perform_attack, + perform_mask_attack, data, payload=package, max_workers=max_workers, - timeout=ATTACK_TIMEOUT, + timeout=MASK_ATTACK_TIMEOUT, ) password = None From a0b544c40910051e5641a51084c51678630ab372 Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Fri, 28 May 2021 14:16:06 +0200 Subject: [PATCH 12/16] Update argument parser help text --- examples/yacat/yacat.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index d59750e5e..4cc30af61 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -31,10 +31,18 @@ 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") +arg_parser = build_parser("Run a hashcat attack (mask mode) on Golem network.") +arg_parser.epilog = ( + "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) arg_parser.add_argument( - "--mask", type=str, help="Hashcat mask to be used for the attack", required=True + "--mask", + type=str, + 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", + required=True, ) arg_parser.add_argument( "--chunk-size", # affects skip and limit hashcat parameters @@ -45,7 +53,7 @@ arg_parser.add_argument( "--hash-type", type=int, - help="Type of hashing algorithm to use (hashcat -m option)", + help="Type of hashing algorithm to use (hashcat -m option). Default: 400 (phpass)", default=400, ) arg_parser.add_argument( From 2ab9620498312b4cb24e98388c273f834b1b1aca Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Fri, 28 May 2021 14:21:59 +0200 Subject: [PATCH 13/16] Catch CommandExecutionError in compute_keyspace --- examples/yacat/yacat.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index 4cc30af61..c9febcb40 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -5,12 +5,13 @@ from pathlib import Path import sys from tempfile import NamedTemporaryFile -from typing import AsyncIterable, Optional +from typing import AsyncIterable, List, Optional from yapapi import Golem, NoPaymentAccountError, Task, WorkContext, windows_event_loop_fix from yapapi.executor.events import CommandExecuted from yapapi.log import enable_default_logger, log_summary, log_event_repr # noqa from yapapi.payload import vm +from yapapi.rest.activity import CommandExecutionError examples_dir = Path(__file__).resolve().parent.parent sys.path.append(str(examples_dir)) @@ -77,18 +78,18 @@ async def compute_keyspace(context: WorkContext, tasks: AsyncIterable[Task]): cmd = f"hashcat --keyspace " f"-a {HASHCAT_ATTACK_MODE} -m {args.hash_type} {args.mask}" context.run("/bin/bash", "-c", cmd) - future_result = yield context.commit(timeout=KEYSPACE_TIMEOUT) - # 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] + try: + future_result = yield context.commit(timeout=KEYSPACE_TIMEOUT) - if cmd_result.success: - keyspace = int(cmd_result.stdout) - else: - raise RuntimeError("Failed to compute attack keyspace") + # 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] - task.accept_result(result=keyspace) + 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]): From ff2e182834ddbb931cd1bec3388541673624bb49 Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Fri, 28 May 2021 18:16:47 +0200 Subject: [PATCH 14/16] Adapt yacat integration test to new implementation --- tests/goth/test_run_yacat.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/goth/test_run_yacat.py b/tests/goth/test_run_yacat.py index d92c71265..042500953 100644 --- a/tests/goth/test_run_yacat.py +++ b/tests/goth/test_run_yacat.py @@ -1,4 +1,5 @@ import logging +import math import os from pathlib import Path import re @@ -14,7 +15,10 @@ logger = logging.getLogger("goth.test.run_yacat") -ALL_TASKS = {"0", "48", "None"} +EXPECTED_KEYSPACE_SIZE = 95 +PROVIDER_COUNT = 2 +CHUNK_SIZE = math.ceil(EXPECTED_KEYSPACE_SIZE / PROVIDER_COUNT) +ALL_TASKS = {"compute_keyspace", "0", f"{CHUNK_SIZE}"} # Temporal assertions expressing properties of sequences of "events". In this case, each "event" @@ -33,7 +37,7 @@ async def assert_all_tasks_processed(status: str, output_lines: EventStream[str] remaining_tasks = ALL_TASKS.copy() async for line in output_lines: - m = re.search(rf".*Task {status} .* task data: ([a-zA-Z0-9]+)$", line) + m = re.search(rf".*Task {status} .* task data: (.+)$", line) if m: task_data = m.group(1) logger.debug("assert_all_tasks_processed: Task %s: %s", status, task_data) @@ -96,7 +100,8 @@ async def test_run_yacat(log_dir: Path, project_dir: Path, config_overrides) -> requestor = runner.get_probes(probe_type=RequestorProbe)[0] async with requestor.run_command_on_host( - f"{yacat_path} ?a?a $P$5ZDzPE45CigTC6EY4cXbyJSLj/pGee0 --number-of-providers 2 --log-file yacat-debug.log --subnet-tag goth", + f"{yacat_path} --mask ?a?a --hash $P$5ZDzPE45CigTC6EY4cXbyJSLj/pGee0 " + f"--subnet-tag goth --chunk-size {CHUNK_SIZE}", env=os.environ, ) as (_cmd_task, cmd_monitor): @@ -106,10 +111,12 @@ async def test_run_yacat(log_dir: Path, project_dir: Path, config_overrides) -> all_sent = cmd_monitor.add_assertion(assert_all_tasks_started) all_computed = cmd_monitor.add_assertion(assert_all_tasks_computed) - await cmd_monitor.wait_for_pattern(".*The keyspace size is 95", timeout=120) + await cmd_monitor.wait_for_pattern( + f".*The keyspace size is {EXPECTED_KEYSPACE_SIZE}", timeout=120 + ) logger.info("Keyspace found") - await cmd_monitor.wait_for_pattern(".*Received proposals from 2 ", timeout=10) + await cmd_monitor.wait_for_pattern(".*Received proposals from 2", timeout=10) logger.info("Received proposals") await all_sent.wait_for_result(timeout=30) From 9403a0418e1fcc5d7d5bb062f8d5711daff1ed0b Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Tue, 1 Jun 2021 12:45:25 +0200 Subject: [PATCH 15/16] Fix max_workers calculation --- examples/yacat/yacat.py | 3 ++- tests/goth/test_run_yacat.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/yacat/yacat.py b/examples/yacat/yacat.py index c9febcb40..467a8d9b2 100755 --- a/examples/yacat/yacat.py +++ b/examples/yacat/yacat.py @@ -2,6 +2,7 @@ import argparse import asyncio from datetime import datetime, timedelta +import math from pathlib import Path import sys from tempfile import NamedTemporaryFile @@ -180,7 +181,7 @@ async def main(args): ) data = [Task(data=c) for c in range(0, keyspace, args.chunk_size)] - max_workers = args.max_workers or (keyspace // args.chunk_size) // 2 + max_workers = args.max_workers or math.ceil(keyspace / args.chunk_size) // 2 completed = golem.execute_tasks( perform_mask_attack, diff --git a/tests/goth/test_run_yacat.py b/tests/goth/test_run_yacat.py index 042500953..17b5de160 100644 --- a/tests/goth/test_run_yacat.py +++ b/tests/goth/test_run_yacat.py @@ -101,7 +101,7 @@ async def test_run_yacat(log_dir: Path, project_dir: Path, config_overrides) -> async with requestor.run_command_on_host( f"{yacat_path} --mask ?a?a --hash $P$5ZDzPE45CigTC6EY4cXbyJSLj/pGee0 " - f"--subnet-tag goth --chunk-size {CHUNK_SIZE}", + f"--subnet-tag goth --chunk-size {CHUNK_SIZE} --max-workers {PROVIDER_COUNT}", env=os.environ, ) as (_cmd_task, cmd_monitor): From 345f31bc8fca87447bfc9b1bea4a8ec647b4a7d1 Mon Sep 17 00:00:00 2001 From: Kuba Mazurek Date: Tue, 1 Jun 2021 13:23:01 +0200 Subject: [PATCH 16/16] Increase yacat test computation timeout --- tests/goth/test_run_yacat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/goth/test_run_yacat.py b/tests/goth/test_run_yacat.py index 17b5de160..96a2b2360 100644 --- a/tests/goth/test_run_yacat.py +++ b/tests/goth/test_run_yacat.py @@ -122,7 +122,7 @@ async def test_run_yacat(log_dir: Path, project_dir: Path, config_overrides) -> await all_sent.wait_for_result(timeout=30) logger.info("All tasks sent") - await all_computed.wait_for_result(timeout=30) + await all_computed.wait_for_result(timeout=60) logger.info("All tasks computed") await cmd_monitor.wait_for_pattern(".*Password found: yo", timeout=10)