-
Notifications
You must be signed in to change notification settings - Fork 808
Ransomware bitflip encryption #1264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
LOG.info(f"Windows dir configured for encryption is " f"{config['windows_dir']}") | ||
LOG.info(f"Linux dir configured for encryption is " f"{config['linux_dir']}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be moved/remove in this PR, because it's where they actually get used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think logging this information is still valuable.
self._target_dir = Path(config["windows_dir"] if is_windows_os() else config["linux_dir"]) | ||
self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() | ||
self._valid_file_extensions_for_encryption.discard(EXTENSION) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be moved to separate class which represents an action?
self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() | ||
self._valid_file_extensions_for_encryption.discard(EXTENSION) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just remove EXTENSION
from the VALID_FILE_EXTENSIONS_FOR_ENCRYPTION
list?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this doing, actually? VALID_FILE_EXTENSIONS_FOR_ENCRYPTION
doesn't have m0nk3y
in it. Why are you trying to discard it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a little bit preemptive/defensive. When we implement #1242, we'll want to ensure that the extension isn't in the set of extensions we'll encrypt. I wrote the code this way to avoid introducing bugs later on.
|
||
def _encrypt_file(self, filepath): | ||
with open(filepath, "rb+") as f: | ||
data = f.read(CHUNK_SIZE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we read in chunks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So that if we're encrypting a 30GB file we don't run out of RAM.
f.seek(-num_bytes_read, 1) | ||
f.write(encrypted_data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be moved into a method with an explanatory name? Like overwrite_bytes_in_file
or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you think it's still necessary. It now lives inside _encrypt_single_file_in_place()
. The "in_place" portion of the name better indicates what's going on. IMHO, this function is short and focused enough that with the new name, I think it's clear what's going in. But I can still change it if you feel strongly about it.
] | ||
|
||
all_files = get_all_regular_files_in_directory(self._target_dir) | ||
return filter_files(all_files, file_filters) | ||
|
||
def _encrypt_files(self, file_list): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fact that this method and the methods that get called by this method doesn't use self
indicates that this shouldn't be in a different class or at least static methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed as part of a refactor.
|
||
from infection_monkey.ransomware.ransomware_payload import RansomewarePayload | ||
|
||
EXTENSION = ".m0nk3y" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we just import the extension from monkey/infection_monkey/ransomware/ransomware_payload.py
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Where are we checking if ransomware is configured or not? If those fields in the config are empty,
Path('')
will default to the root directory (at least on Linux, it will). All files in the root directory will be encrypted. - Tests fail:
TypeError: copytree() got an unexpected keyword argument 'dirs_exist_ok'
.
self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() | ||
self._valid_file_extensions_for_encryption.discard(EXTENSION) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this doing, actually? VALID_FILE_EXTENSIONS_FOR_ENCRYPTION
doesn't have m0nk3y
in it. Why are you trying to discard it?
self.config = config | ||
LOG.info(f"Windows dir configured for encryption is " f"{config['windows_dir']}") | ||
LOG.info(f"Linux dir configured for encryption is " f"{config['linux_dir']}") | ||
|
||
def run_payload(self): | ||
LOG.info( | ||
f"Windows dir configured for encryption is " f"{self.config['windows_dir_ransom']}" | ||
) | ||
LOG.info(f"Linux dir configured for encryption is " f"{self.config['linux_dir_ransom']}") | ||
self._target_dir = Path(config["windows_dir"] if is_windows_os() else config["linux_dir"]) | ||
self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() | ||
self._valid_file_extensions_for_encryption.discard(EXTENSION) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're not having an if
statement in monkey.py
, we're running all this code in any case, ransomware configured or not, which doesn't really make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had mistakenly assumed that if the path was empty, no files would be found. In fact, it appears that Path("")
evaluates to Path(".")
. Good catch.
def run_payload(self): | ||
file_list = self._find_files() | ||
self._encrypt_files(file_list) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should return the data about the run. List of tuples with each filename and its result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should send the appropriate telemetry. For the moment, we're going to avoid having payloads return anything.
4dda012
to
447138c
Compare
The dirs_exist_ok parameter of shutil.copytree() was introduced in Python 3.8. Since the agent uses python3.7 in order to be more compatible with older systems, we can't use this parameter.
The larger chunk size improves efficiency by reducing the number of reads.
The .gitignore file prevents dlls from being added to git. Since this isn't a real dll, but is only used for testing, we can add it anyway.
Codecov Report
@@ Coverage Diff @@
## develop #1264 +/- ##
===========================================
+ Coverage 29.47% 29.97% +0.50%
===========================================
Files 434 441 +7
Lines 13184 13283 +99
===========================================
+ Hits 3886 3982 +96
- Misses 9298 9301 +3
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few questions
What does this PR do?
Fixes #1238.
Add any further explanations here.
PR Checklist
Testing Checklist
Explain Changes
Are the commit messages enough? If not, elaborate.