Skip to content

Detect strings written to global memory - #1335

Open
amancslab wants to merge 1 commit into
mandiant:masterfrom
amancslab:fix/global-stackstrings
Open

Detect strings written to global memory#1335
amancslab wants to merge 1 commit into
mandiant:masterfrom
amancslab:fix/global-stackstrings

Conversation

@amancslab

Copy link
Copy Markdown

Summary

Detect strings constructed character-by-character in global memory during stack-string emulation.

The stack-string emulator already enables Vivisect memory-write logging. This change uses the write log to:

  • identify non-stack addresses written during function emulation
  • group adjacent written addresses into memory regions
  • extract strings from the completed global-memory regions
  • report them as DecodedString results with AddressType.GLOBAL
  • preserve the existing extract_stackstrings() API
  • avoid duplicate decoded-string results when the existing decoder pipeline finds the same string

This resolves the global stackstrings case described in #37.

Testing

python -m pytest tests/data/src/decode-global-stackstrings/test.yml --runxfail -vv

Result:

3 passed

The test passed for:

  • Linux
  • Windows 32-bit
  • Windows 64-bit

Additional regression tests:

python -m pytest tests/test_strings.py tests/test_load.py tests/test_render.py -vv

Result:

5 passed

Formatting hooks passed:

  • isort
  • black
  • git diff --check

The mypy hook still reports the existing Windows-specific mmap typing errors in floss/utils.py:617; no new typing errors are introduced by this change.

Companion test-data PR: mandiant/flare-floss-testfiles#27

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the extraction of global strings written during emulation by extending the stackstrings extraction process. It adds a helper function get_written_global_memory to identify written non-stack memory ranges and extracts strings from these regions, updating the main analysis flow and tests accordingly. The reviewer identified critical correctness, performance, and robustness issues in get_written_global_memory. Specifically, using getAllPaths instead of getPathToNode includes backtracked branches, which can lead to false positives or corrupted strings, and degrades performance. Additionally, the reviewer recommended wrapping emu.readMemory in a try...except block to prevent potential crashes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread floss/stackstrings.py
Comment on lines +56 to +83
for path_node in vg_path.getAllPaths(emu.path):
for _parent, _children, node_data in path_node:
for _opva, refva, written_data in node_data.get("writelog", []) or []:
for address in range(refva, refva + len(written_data)):
if stack_start <= address < stack_end:
continue

if emu.vw.isValidPointer(address):
written_addresses.add(address)

if not written_addresses:
return []

regions: List[Tuple[int, bytes]] = []
sorted_addresses = sorted(written_addresses)
region_start = sorted_addresses[0]
previous = sorted_addresses[0]

for address in sorted_addresses[1:]:
if address != previous + 1:
size = previous - region_start + 1
regions.append((region_start, emu.readMemory(region_start, size)))
region_start = address

previous = address

size = previous - region_start + 1
regions.append((region_start, emu.readMemory(region_start, size)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Correctness, Performance, and Robustness Issues in Global Memory Extraction

There are three major issues with the current implementation of get_written_global_memory:

  1. Correctness (False Positives/Corrupted Strings): Using vg_path.getAllPaths(emu.path) retrieves the writelogs of all branches in the path tree, including sibling branches that were backtracked. Since those backtracked branches are not part of the current execution path, their writes are not in the emulator's memory. Reading those addresses from the current emulator memory state will read uninitialized/unrelated data, leading to incorrect string extraction.
  2. Performance (Quadratic Complexity): getAllPaths traverses the entire path tree, which can be extremely large in full coverage emulation, leading to quadratic complexity and severe performance degradation as emulation progresses.
  3. Robustness (Potential Crashes): emu.readMemory can raise exceptions (e.g., SegmentationViolation or generic Exception) if memory is unmapped or protected. Wrapping it in a try...except block prevents FLOSS from crashing during emulation.

Solution

Using vg_path.getPathToNode(emu.path, emu.path) correctly retrieves only the nodes along the current active execution path, which is both correct and significantly faster. Additionally, wrapping emu.readMemory in a try...except block ensures robustness.

    for _nodeid, _parentid, node_data in vg_path.getPathToNode(emu.path, emu.path):
        for _opva, refva, written_data in node_data.get("writelog", []) or []:
            for address in range(refva, refva + len(written_data)):
                if stack_start <= address < stack_end:
                    continue

                if emu.vw.isValidPointer(address):
                    written_addresses.add(address)

    if not written_addresses:
        return []

    regions: List[Tuple[int, bytes]] = []
    sorted_addresses = sorted(written_addresses)
    region_start = sorted_addresses[0]
    previous = sorted_addresses[0]

    for address in sorted_addresses[1:]:
        if address != previous + 1:
            size = previous - region_start + 1
            try:
                regions.append((region_start, emu.readMemory(region_start, size)))
            except Exception as e:
                logger.debug("failed to read memory at 0x%x (size: %d): %s", region_start, size, e)
            region_start = address

        previous = address

    size = previous - region_start + 1
    try:
        regions.append((region_start, emu.readMemory(region_start, size)))
    except Exception as e:
        logger.debug("failed to read memory at 0x%x (size: %d): %s", region_start, size, e)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant