diff --git a/doc/installation.md b/doc/installation.md index 3e42d12d9..070214a46 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -77,6 +77,9 @@ This means that Python will load the FLOSS module from this local This is good, because it is easy for us to modify files and see the effects reflected immediately. But be careful not to remove this directory unless uninstalling FLOSS! +If you encounter the error `ERROR: Project has a 'pyproject.toml' and its build backend is missing the 'build_editable' hook.`, + please ensure that you have upgraded to the latest versions of pip and setuptools. + - Install FLOSS: diff --git a/doc/usage.md b/doc/usage.md index 49c86a777..41ec45f23 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -140,6 +140,16 @@ Specify functions by using their hex-encoded virtual address. floss.exe --functions 0x401000 0x402000 malware.exe +### Install/Uninstall right click menu option for Windows (`--install-right-click-menu/--uninstall-right-click-menu`) + +You can use the `--install-right-click-menu` and `--uninstall-right-click-menu` + options to install/remove the `Open with FLOSS` option from the right-click menu + of the Windows file explorer. + +After this option is installed, you can right-click on any file and select `Open with FLOSS` + to quickly open the target file with FLOSS for analysis. + + ## Shellcode analysis options Malicious shellcode often times contains obfuscated strings or stackstrings. diff --git a/floss/main.py b/floss/main.py index 0e5ccdc2c..bc0622de4 100644 --- a/floss/main.py +++ b/floss/main.py @@ -144,7 +144,6 @@ def make_parser(argv): ) parser.register("action", "extend", floss.utils.ExtendAction) parser.add_argument("-H", action="help", help="show advanced options and exit") - parser.add_argument( "-n", "--minimum-length", @@ -254,6 +253,26 @@ def make_parser(argv): version="%(prog)s {:s}".format(__version__), help="show program's version number and exit" if show_all_options else argparse.SUPPRESS, ) + if sys.platform == "win32": + advanced_group.add_argument( + "--install-right-click-menu", + action=floss.utils.InstallContextMenu, + help=( + "install FLOSS to the right-click context menu for Windows Explorer and exit" + if show_all_options + else argparse.SUPPRESS + ), + ) + + advanced_group.add_argument( + "--uninstall-right-click-menu", + action=floss.utils.UninstallContextMenu, + help=( + "uninstall FLOSS from the right-click context menu for Windows Explorer and exit" + if show_all_options + else argparse.SUPPRESS + ), + ) output_group = parser.add_argument_group("rendering arguments") output_group.add_argument("-j", "--json", action="store_true", help="emit JSON instead of text") diff --git a/floss/utils.py b/floss/utils.py index 895bd7c95..e74e5a177 100644 --- a/floss/utils.py +++ b/floss/utils.py @@ -1,5 +1,7 @@ # Copyright (C) 2017 Mandiant, Inc. All Rights Reserved. +import os import re +import sys import mmap import time import inspect @@ -42,6 +44,62 @@ def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, items) +class InstallContextMenu(argparse.Action): + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(InstallContextMenu, self).__init__(option_strings, dest, nargs=0, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + # Theoretically, we don't need to check the platform again here, + # because non-Windows platforms will not accept the --install-right-click-menu parameter at all. + # This judgment is just to make the mypy type check pass. + # The same logic applies to `UninstallContextMenu` below. + if sys.platform == "win32": + import winreg as reg + + menu_name = "Open with FLOSS" + icon_path = None + + if getattr(sys, "frozen", False): + # If this is a standalone floss.exe, the path to the floss is sys.executable + menu_command = f'C:\\windows\\system32\\cmd.exe /K "^"{sys.executable}^" ^"%1^""' + icon_path = sys.executable + else: + menu_command = f'C:\\windows\\system32\\cmd.exe /K "python -m floss ^"%1^""' + + # Create `shell` if it does not exist + try: + shell_key = reg.OpenKey(reg.HKEY_CURRENT_USER, r"Software\\Classes\\*\\shell", 0, reg.KEY_SET_VALUE) + except FileNotFoundError: + shell_key = reg.CreateKey(reg.HKEY_CURRENT_USER, r"Software\\Classes\\*\\shell") + shell_key = reg.OpenKey(reg.HKEY_CURRENT_USER, r"Software\\Classes\\*\\shell", 0, reg.KEY_SET_VALUE) + + reg.SetValue(shell_key, menu_name, reg.REG_SZ, menu_name) + + menu_key = reg.OpenKey(shell_key, menu_name, 0, reg.KEY_SET_VALUE) + if icon_path: + reg.SetValueEx(menu_key, "Icon", 0, reg.REG_SZ, icon_path) + reg.SetValue(menu_key, "command", reg.REG_SZ, menu_command) + sys.exit(0) + + +class UninstallContextMenu(argparse.Action): + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(UninstallContextMenu, self).__init__(option_strings, dest, nargs=0, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + if sys.platform == "win32": + import winreg as reg + + menu_name = "Open with FLOSS" + + shell_key = reg.OpenKey(reg.HKEY_CURRENT_USER, r"Software\\Classes\\*\\shell") + menu_key = reg.OpenKey(shell_key, menu_name) + + reg.DeleteKey(menu_key, "command") + reg.DeleteKey(shell_key, menu_name) + sys.exit(0) + + def set_vivisect_log_level(level) -> None: logging.getLogger("vivisect").setLevel(level) logging.getLogger("vivisect.base").setLevel(level)