diff --git a/plugins/fzuse.rb b/plugins/fzuse.rb index d23023350b..cb8918c012 100644 --- a/plugins/fzuse.rb +++ b/plugins/fzuse.rb @@ -1,3 +1,18 @@ +require 'socket' + +# this is the main routine that's executed in the grandchild process (msfconsole -> fzf -> this) +if $PROGRAM_NAME == __FILE__ + exit 64 unless ARGV.length == 2 + + UNIXSocket.open(ARGV[0]) do |sock| + sock.write ARGV[1] + "\n" + sock.flush + + puts sock.read + end + exit 0 +end + module Msf ### # @@ -39,37 +54,60 @@ module Msf } end + def start_pipe_server(socket_path) + def pipe_server(socket_path) + server = UNIXServer.new(socket_path) + File.chmod(0600, socket_path) + loop do + client = server.accept + unless (input_string = client.gets&.chomp).blank? + if (mod = framework.modules.create(input_string)) + client.puts(Serializer::ReadableText.dump_module(mod)) + end + end + client.close + end + rescue EOFError + ensure + server.close if server + File.delete(socket_path) if File.exist?(socket_path) + end + + Thread.new do + pipe_server(socket_path) + end + end + # # This method handles the fuzzy_use command. # def cmd_fzuse(*args) - previewer = File.join(Msf::Config.install_root, 'tools', 'modules', 'print.py') - metadata_path = Msf::Modules::Metadata::Cache.instance.get_user_store - - module_types = framework.modules.module_types - - query = args.empty? ? '' : args.first - selection = nil - # alternative preview: - # jq \'to_entries[] | select(.value.fullname == "{1}") | .value\' db/modules_metadata_base.json | bat --language=json --color=always - stdin, stdout, stderr, wait_thr = Open3.popen3('fzf', '--select-1', '--query', query, '--preview', "#{previewer} --metadata-path '#{metadata_path}' '{1}'", '--preview-label', "Module Information") do |stdin, stdout, stderr, wait_thr| - module_types - module_types.each do |module_type| - framework.modules.module_names(module_type).each do |module_name| - stdin.puts "#{module_type}/#{module_name}" - end - end - stdin.close - exit_status = wait_thr.value - - selection = stdout.read + Dir.mktmpdir('msf-fzuse-') do |dir| + File.chmod(0700, dir) + socket_path = File.join(dir, "msf-fzuse.sock") + server_thread = start_pipe_server(socket_path) + + query = args.empty? ? '' : args.first + ruby = RbConfig::CONFIG['bindir'] + '/' + RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT'] + + Open3.popen3('fzf', '--select-1', '--query', query, '--preview', "'#{ruby}' '#{__FILE__}' '#{socket_path}' '{1}'", '--preview-label', "Module Information") do |stdin, stdout, stderr, wait_thr| + framework.modules.module_types.each do |module_type| + framework.modules.module_names(module_type).each do |module_name| + stdin.puts "#{module_type}/#{module_name}" + end + end + stdin.close + selection = stdout.read + end + + server_thread.kill end - selection.strip! return if selection.blank? - + + selection.strip! @module_dispatcher.cmd_use(selection) end end @@ -83,11 +121,9 @@ module Msf # def initialize(framework, opts) super - + missing_requirements = [] missing_requirements << 'fzf' unless Msf::Util::Helper.which('fzf') - missing_requirements << 'python' unless Msf::Util::Helper.which('python') - missing_requirements << 'python-rich' unless system("python -c 'import rich'", out: File::NULL, err: File::NULL) unless missing_requirements.empty? print_error("The FuzzyUse plugin has loaded but the following requirements are missing: #{missing_requirements.join(', ')}") diff --git a/tools/modules/print.py b/tools/modules/print.py deleted file mode 100755 index 3fc9270163..0000000000 --- a/tools/modules/print.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python -import argparse -import json -import pathlib - -from rich import box -from rich.console import Console -from rich.panel import Panel -from rich.syntax import Syntax -from rich.table import Table -from rich.tree import Tree - -__version__ = '1.0' - -RANKS = { - 600: 'Excellent', - 500: 'Great', - 400: 'Good', - 300: 'Normal', - 200: 'Average', - 100: 'Low', - 0: 'Manual' -} - -framework_root = pathlib.Path(__file__).parent.parent.parent -default_metadata_path = (framework_root / 'db' / 'modules_metadata_base.json') - -def get_notes(module_metadata): - tree = Tree('Notes', hide_root=True) - for key, values in module_metadata.get('notes', {}).items(): - node = tree.add(f"[italic]{key}[/italic]") - for value in values: - node.add(value) - return tree - -def get_description(module_metadata): - description = '' - paragraphs = module_metadata['description'].split('\n\n') - for paragraph in paragraphs: - for line in paragraph.split('\n'): - description += line.strip() + '\n' - description += '\n' - return description.strip() - -def get_authors(module_metadata): - return get_bulleted_list(module_metadata['author']) - -def get_targets(module_metadata): - return get_bulleted_list(module_metadata['targets']) - -def get_references(module_metadata): - references = [] - for reference in module_metadata.get('references', []): - if reference.startswith('URL-'): - reference = reference[4:] - references.append(reference) - return get_bulleted_list(references) - -def get_bulleted_list(items): - formatted = '' - for item in items: - formatted += f"[bold]•[/bold] {item}\n" - return formatted.strip() - -def main(): - parser = argparse.ArgumentParser(description='fzuse helper', conflict_handler='resolve') - parser.add_argument('module_name', help='module name to display') - parser.add_argument('--metadata-path', default=default_metadata_path, type=pathlib.Path, help='the path to the module metadata') - parser.add_argument('-v', '--version', action='version', version='%(prog)s Version: ' + __version__) - arguments = parser.parse_args() - - with arguments.metadata_path.open('r') as file_h: - all_metadata = json.load(file_h) - module_metadata = next((metadata for metadata in all_metadata.values() if metadata['fullname'] == arguments.module_name), None) - if not module_metadata: - return - - table = Table(show_header=False, box=box.MINIMAL) - table.add_column(justify='right') - table.add_column() - - table.add_row('[bold yellow]Name[/bold yellow]', module_metadata['name']) - table.add_row('[bold yellow]Module[/bold yellow]', module_metadata['fullname']) - table.add_row('[bold yellow]Platform[/bold yellow]', module_metadata['platform']) - table.add_row('[bold yellow]Arch[/bold yellow]', module_metadata['arch']) - table.add_row('[bold yellow]Rank[/bold yellow]', RANKS[module_metadata['rank']]) - table.add_row('[bold yellow]Disclosed[/bold yellow]', module_metadata['disclosure_date']) - - console = Console(color_system='256') - console.print(table) - - panel_title = lambda v: f"[bold yellow]{v}[/bold yellow]" - console.print(Panel(get_authors(module_metadata), title=panel_title('Provided by'), title_align='left')) - console.print(Panel(get_notes(module_metadata), title=panel_title('Notes'), title_align='left')) - if module_metadata.get('targets'): - console.print(Panel(get_targets(module_metadata), title=panel_title('Targets'), title_align='left')) - console.print(Panel(get_description(module_metadata), title=panel_title('Description'), title_align='left')) - if module_metadata.get('references'): - console.print(Panel(get_references(module_metadata), title=panel_title('References'), title_align='left')) - if module_metadata.get('path', ''): - if pathlib.Path(module_metadata['path']).is_file(): - module_path = pathlib.Path(module_metadata['path']) - elif pathlib.Path(framework_root / module_metadata['path'][1:]).is_file(): - module_path = pathlib.Path(framework_root / module_metadata['path'][1:]) - if module_path: - syntax = Syntax.from_path(module_path, background_color='default', line_numbers=True) - console.print(Panel(syntax, title=panel_title('Source code'), title_align='left')) - -if __name__ == '__main__': - main()