mirror of
https://github.com/vee1e/flare-floss.git
synced 2026-09-01 09:49:59 +00:00
167 lines
5.7 KiB
Python
167 lines
5.7 KiB
Python
# Copyright 2026 Google LLC
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import copy
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import floss.render.json
|
|
from floss.tags import load_databases
|
|
from floss.enrich import static_strings_from_layout
|
|
from floss.layout import compute_layout
|
|
from floss.ranges import Slice
|
|
from floss.results import Strings, Analysis, Metadata, ResultLayout, ResultDocument
|
|
from floss.layout.extract import collect_strings
|
|
|
|
CD = Path(__file__).resolve().parent
|
|
MIN_STR_LEN = 6
|
|
|
|
|
|
@pytest.fixture
|
|
def pma_binary_path():
|
|
return CD / "data" / "pma" / "Practical Malware Analysis Lab 03-03.exe_"
|
|
|
|
|
|
@pytest.fixture
|
|
def analyzed_layout(pma_binary_path):
|
|
slice_buf = pma_binary_path.read_bytes()
|
|
file_slice = Slice.from_bytes(slice_buf)
|
|
parsed = compute_layout(file_slice)
|
|
parsed.extract_strings(6)
|
|
taggers = load_databases()
|
|
parsed.tag_strings(taggers)
|
|
parsed.mark_structures()
|
|
return parsed
|
|
|
|
|
|
def test_round_trip(analyzed_layout, pma_binary_path):
|
|
layout_doc = ResultLayout.from_layout(analyzed_layout)
|
|
statics = static_strings_from_layout(layout_doc)
|
|
one = ResultDocument(
|
|
metadata=Metadata(file_path=str(pma_binary_path.resolve()), min_length=MIN_STR_LEN),
|
|
analysis=Analysis(
|
|
enable_static_strings=True,
|
|
enable_stack_strings=False,
|
|
enable_tight_strings=False,
|
|
enable_decoded_strings=False,
|
|
enable_layout=True,
|
|
enable_tags=True,
|
|
),
|
|
strings=Strings(static_strings=statics),
|
|
layout=layout_doc,
|
|
)
|
|
|
|
doc = floss.render.json.render(one)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as f:
|
|
f.write(doc)
|
|
path = Path(f.name)
|
|
try:
|
|
two = ResultDocument.parse_file(path)
|
|
finally:
|
|
path.unlink()
|
|
|
|
# show the round trip works
|
|
assert one == two
|
|
assert floss.render.json.render(one) == floss.render.json.render(two)
|
|
|
|
# now show that two different versions are not equal.
|
|
three = copy.deepcopy(two)
|
|
three.metadata.version = "0"
|
|
assert two.metadata.version != three.metadata.version
|
|
assert floss.render.json.render(two) != floss.render.json.render(three)
|
|
|
|
|
|
def test_string_extraction(analyzed_layout):
|
|
strings = collect_strings(analyzed_layout)
|
|
# Check if a known string is extracted
|
|
assert any(s.string.string == "user32.dll" for s in strings)
|
|
|
|
|
|
def test_tagging(analyzed_layout):
|
|
strings = collect_strings(analyzed_layout)
|
|
# Check if a known string is tagged correctly
|
|
user32_string = next(s for s in strings if s.string.string == "user32.dll")
|
|
assert "#winapi" in user32_string.tags
|
|
|
|
|
|
def test_structure_marking(analyzed_layout):
|
|
strings = collect_strings(analyzed_layout)
|
|
# Check if a string is correctly associated with a structure
|
|
data_string = next(s for s in strings if s.string.string == "@.data")
|
|
assert data_string.structure == "section header"
|
|
|
|
close_string = next(s for s in strings if s.string.string == "CloseHandle")
|
|
assert close_string.structure == "import table"
|
|
|
|
|
|
def test_analysis_pipeline(pma_binary_path):
|
|
# Run the analysis pipeline
|
|
slice_buf = pma_binary_path.read_bytes()
|
|
file_slice = Slice.from_bytes(slice_buf)
|
|
parsed = compute_layout(file_slice)
|
|
parsed.extract_strings(6)
|
|
|
|
# Check that the layout has been computed correctly
|
|
assert parsed.name == "pe"
|
|
|
|
|
|
def test_is_structured_layout():
|
|
import floss.enrich
|
|
|
|
assert floss.enrich.is_structured_layout("pe")
|
|
assert floss.enrich.is_structured_layout("elf")
|
|
assert floss.enrich.is_structured_layout("macho")
|
|
assert floss.enrich.is_structured_layout("macho (fat)")
|
|
# XOR-obfuscated PE/ELF headers append the XOR note to the name
|
|
assert floss.enrich.is_structured_layout("pe (XOR decoded with key: 0x41)")
|
|
assert floss.enrich.is_structured_layout("elf (XOR decoded with key: 0x42)")
|
|
assert not floss.enrich.is_structured_layout("binary")
|
|
|
|
|
|
def _make_root_layout(cls, name, **extra):
|
|
from floss.ranges import Range, Slice, OffsetRanges
|
|
from floss.layout.base import Structure
|
|
from floss.layout.types import TaggedString, ExtractedString
|
|
|
|
buf = b"\x00" * 32
|
|
sl = Slice(buf=buf, range=Range(offset=0, length=len(buf)), base_offset=0)
|
|
layout = cls(
|
|
name=name,
|
|
slice=sl,
|
|
structures_by_address={0: Structure(slice=sl, name="pe/elf header")},
|
|
reloc_offsets=OffsetRanges(ranges=[]),
|
|
code_offsets=OffsetRanges(ranges=[]),
|
|
**extra,
|
|
)
|
|
layout.strings = [TaggedString(string=ExtractedString(string="rootstr", slice=sl, encoding="ascii"), tags=set())]
|
|
return layout
|
|
|
|
|
|
def test_pe_root_strings_get_structure_annotations():
|
|
from floss.layout.base import PELayout
|
|
|
|
layout = _make_root_layout(PELayout, "pe", xor_key=None)
|
|
layout.mark_structures()
|
|
assert layout.strings[0].structure == "pe/elf header"
|
|
|
|
|
|
def test_elf_root_strings_get_structure_annotations():
|
|
from floss.ranges import OffsetRanges
|
|
from floss.layout.base import ELFLayout
|
|
|
|
layout = _make_root_layout(ELFLayout, "elf", xor_key=None, relocation_offsets=OffsetRanges(ranges=[]))
|
|
layout.mark_structures()
|
|
assert layout.strings[0].structure == "pe/elf header"
|