mirror of
https://github.com/vee1e/flare-floss.git
synced 2026-09-01 17:57:06 +00:00
feat(cli): make string deobfuscation opt-in and drop the prompt
The interactive deobfuscation prompt (and the confusing -y/--yes / --no-prompt flag that skipped it) is removed entirely. String deobfuscation is now opt-in: a plain run defaults to static (and language) strings only, and stack/tight/decoded must be requested explicitly via --string-type. --analyze-functions requests function-level deobfuscation, so it is not disabled by the default. This resolves the silent redirect behavior from #1176: there is no implicit 'n' anymore, just an explicit default.
This commit is contained in:
parent
f700666757
commit
0abe0c2103
4 changed files with 19 additions and 62 deletions
|
|
@ -378,13 +378,6 @@ def make_parser():
|
|||
logging_group.add_argument(
|
||||
"-q", "--quiet", action="store_true", help="disable all status output on STDOUT except fatal errors"
|
||||
)
|
||||
logging_group.add_argument(
|
||||
"--no-prompt",
|
||||
dest="no_prompt",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="do not prompt to enable string deobfuscation (defaults to not running it)",
|
||||
)
|
||||
logging_group.add_argument(
|
||||
"--color",
|
||||
type=str,
|
||||
|
|
|
|||
|
|
@ -211,10 +211,13 @@ def main(argv=None) -> int:
|
|||
disabled_string_types = expand_string_types(list(args.disabled_string_types or []))
|
||||
enabled_string_types = expand_string_types(list(args.enabled_string_types or []))
|
||||
|
||||
if args.summary and not disabled_string_types and not enabled_string_types:
|
||||
# the summary's layout-derived sections cover static strings only, so
|
||||
# don't spin up the slow deobfuscation for stack/tight/decoded
|
||||
logger.info("--summary is static-only; skipping stack/tight/decoded extraction")
|
||||
if not enabled_string_types and not disabled_string_types and not args.analyze_functions:
|
||||
# string deobfuscation is opt-in: default to static (and language)
|
||||
# strings only, so a plain run doesn't spin up the slow vivisect stage
|
||||
# for stack/tight/decoded. enable it explicitly with
|
||||
# --string-type stack/tight/decoded (--analyze-functions requests
|
||||
# function-level deobfuscation and is therefore not affected).
|
||||
logger.info("string deobfuscation is off by default; use --string-type stack/tight/decoded to enable")
|
||||
disabled_string_types.extend([StringType.STACK.value, StringType.TIGHT.value, StringType.DECODED.value])
|
||||
|
||||
if args.analyze_functions:
|
||||
|
|
@ -285,7 +288,6 @@ def main(argv=None) -> int:
|
|||
large_file=args.large_file,
|
||||
quiet=args.quiet,
|
||||
verbose=args.verbose,
|
||||
no_prompt=args.no_prompt,
|
||||
cache_dir=cache_dir,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -105,9 +105,6 @@ class Options:
|
|||
large_file: bool = False
|
||||
quiet: bool = False
|
||||
verbose: int = Verbosity.DEFAULT
|
||||
# when True, do not prompt on TTY for deobfuscation on language binaries;
|
||||
# deobfuscation then defaults to not running
|
||||
no_prompt: bool = False
|
||||
# analysis cache directory; None disables caching (default in the CLI is
|
||||
# the platform cache directory via floss.cache.get_cache_dir())
|
||||
cache_dir: Optional[Path] = None
|
||||
|
|
@ -390,41 +387,6 @@ def analyze(options: Options) -> Optional[ResultDocument]:
|
|||
analysis.enable_tight_strings = False
|
||||
analysis.enable_decoded_strings = False
|
||||
|
||||
enabled_string_types = options.enabled_string_types or []
|
||||
disabled_string_types = options.disabled_string_types or []
|
||||
if results.metadata.language not in ("", "unknown"):
|
||||
if not enabled_string_types and not disabled_string_types:
|
||||
if not options.no_prompt:
|
||||
# when stdout is redirected, such as in 'floss foo.exe | less' use default prompt values
|
||||
if sys.stdout.isatty():
|
||||
try:
|
||||
prompt = input(
|
||||
"Do you want to enable string deobfuscation? (this could take a long time) [y/N] "
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
raise PipelineError("aborted by user", exit_code=130)
|
||||
except EOFError:
|
||||
raise PipelineError("aborted by user", exit_code=1)
|
||||
else:
|
||||
prompt = "n"
|
||||
|
||||
if prompt.lower() == "y":
|
||||
logger.info("enabled string deobfuscation")
|
||||
analysis.enable_stack_strings = True
|
||||
analysis.enable_tight_strings = True
|
||||
analysis.enable_decoded_strings = True
|
||||
else:
|
||||
logger.info("disabled string deobfuscation")
|
||||
analysis.enable_stack_strings = False
|
||||
analysis.enable_tight_strings = False
|
||||
analysis.enable_decoded_strings = False
|
||||
else:
|
||||
# --no-prompt: never prompt, default to not running deobfuscation
|
||||
logger.info("string deobfuscation disabled (--no-prompt)")
|
||||
analysis.enable_stack_strings = False
|
||||
analysis.enable_tight_strings = False
|
||||
analysis.enable_decoded_strings = False
|
||||
|
||||
# in order of expected run time, fast to slow
|
||||
# 1. static strings (done above for language ID; layout-aware replace below when enabled)
|
||||
# a) includes language-specific strings, if applicable
|
||||
|
|
|
|||
|
|
@ -39,8 +39,9 @@ def test_shellcode(scfile):
|
|||
assert floss.main.main([scfile, "-f", "sc32"]) == 0
|
||||
assert floss.main.main([scfile, "--format", "sc64"]) == 0
|
||||
|
||||
# fail
|
||||
assert floss.main.main([scfile, "--format", "pe"]) == -1
|
||||
# fail: forcing the PE format on shellcode only errors once deobfuscation
|
||||
# (which runs vivisect) is requested
|
||||
assert floss.main.main([scfile, "--format", "pe", "--string-type", "stack", "tight", "decoded"]) == -1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("type_", [t.value for t in StringType])
|
||||
|
|
@ -65,14 +66,14 @@ def test_args_analysis_type_conflict(exefile):
|
|||
def test_language_extraction_independent_of_static(capsys):
|
||||
"""language strings are extracted even when static strings are disabled.
|
||||
|
||||
uses --string-type language (so only language extraction runs) and --no-prompt so the
|
||||
deobfuscation prompt is skipped, on a Go sample whose language is detectable.
|
||||
uses --string-type language (so only language extraction runs) on a Go sample
|
||||
whose language is detectable, on a Go sample whose language is detectable.
|
||||
"""
|
||||
import json
|
||||
|
||||
sample = Path(__file__).parent / "data" / "language" / "go" / "go-hello" / "bin" / "go-hello64.exe"
|
||||
|
||||
assert floss.main.main([str(sample), "--string-type", "language", "--no-prompt", "-j"]) == 0
|
||||
assert floss.main.main([str(sample), "--string-type", "language", "-j"]) == 0
|
||||
doc = json.loads(capsys.readouterr().out)
|
||||
assert doc["metadata"]["language"] == "go"
|
||||
assert len(doc["strings"]["language_strings"]) > 0
|
||||
|
|
@ -86,7 +87,7 @@ def test_manual_language_override_wins_over_auto_detect(capsys):
|
|||
# a C binary, so auto-detection yields unknown; forcing go must stick
|
||||
sample = Path(__file__).parent / "data" / "src" / "decode-in-place" / "bin" / "test-decode-in-place.exe"
|
||||
|
||||
assert floss.main.main([str(sample), "--language", "go", "--string-type", "language", "--no-prompt", "-j"]) == 0
|
||||
assert floss.main.main([str(sample), "--language", "go", "--string-type", "language", "-j"]) == 0
|
||||
doc = json.loads(capsys.readouterr().out)
|
||||
assert doc["metadata"]["language"] == "go"
|
||||
assert doc["metadata"]["language_selected"] == "go"
|
||||
|
|
@ -106,7 +107,7 @@ def test_manual_language_override_beats_wrong_auto_detect(monkeypatch, capsys):
|
|||
monkeypatch.setattr(floss.language.identify, "identify_language_and_version", fake_identify)
|
||||
|
||||
sample = Path(__file__).parent / "data" / "src" / "decode-in-place" / "bin" / "test-decode-in-place.exe"
|
||||
assert floss.main.main([str(sample), "--language", "go", "--string-type", "language", "--no-prompt", "-j"]) == 0
|
||||
assert floss.main.main([str(sample), "--language", "go", "--string-type", "language", "-j"]) == 0
|
||||
doc = json.loads(capsys.readouterr().out)
|
||||
assert doc["metadata"]["language"] == "go"
|
||||
assert doc["metadata"]["language_version"] == ""
|
||||
|
|
@ -139,7 +140,6 @@ def test_no_layout_yields_classic_static(exefile):
|
|||
enable_layout=False,
|
||||
enable_tags=True,
|
||||
),
|
||||
no_prompt=True,
|
||||
)
|
||||
)
|
||||
assert results is not None
|
||||
|
|
@ -169,7 +169,6 @@ def test_no_tags_skips_tag_databases(exefile):
|
|||
enable_layout=True,
|
||||
enable_tags=False,
|
||||
),
|
||||
no_prompt=True,
|
||||
)
|
||||
)
|
||||
assert results is not None
|
||||
|
|
@ -193,15 +192,16 @@ def test_no_tags_skips_tag_databases(exefile):
|
|||
assert db_tag not in s.tags
|
||||
|
||||
|
||||
def test_no_prompt_skips_deobfuscation_for_go(capsys):
|
||||
"""--no-prompt on a Go sample defaults to not running deobfuscation."""
|
||||
def test_deobfuscation_off_by_default(capsys):
|
||||
"""deobfuscation is opt-in: a plain run leaves stack/tight/decoded off."""
|
||||
import json
|
||||
|
||||
sample = Path(__file__).parent / "data" / "language" / "go" / "go-hello" / "bin" / "go-hello64.exe"
|
||||
|
||||
assert floss.main.main([str(sample), "--no-prompt", "-j"]) == 0
|
||||
assert floss.main.main([str(sample), "-j"]) == 0
|
||||
doc = json.loads(capsys.readouterr().out)
|
||||
assert doc["metadata"]["language"] == "go"
|
||||
assert doc["analysis"]["enable_static_strings"] is True
|
||||
assert doc["analysis"]["enable_stack_strings"] is False
|
||||
assert doc["analysis"]["enable_tight_strings"] is False
|
||||
assert doc["analysis"]["enable_decoded_strings"] is False
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue