Detect strings written to global memory - #1335
Conversation
There was a problem hiding this comment.
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.
| 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))) |
There was a problem hiding this comment.
Correctness, Performance, and Robustness Issues in Global Memory Extraction
There are three major issues with the current implementation of get_written_global_memory:
- 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. - Performance (Quadratic Complexity):
getAllPathstraverses the entire path tree, which can be extremely large in full coverage emulation, leading to quadratic complexity and severe performance degradation as emulation progresses. - Robustness (Potential Crashes):
emu.readMemorycan raise exceptions (e.g.,SegmentationViolationor genericException) if memory is unmapped or protected. Wrapping it in atry...exceptblock 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)
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:
DecodedStringresults withAddressType.GLOBALextract_stackstrings()APIThis resolves the global stackstrings case described in #37.
Testing
Result:
The test passed for:
Additional regression tests:
Result:
Formatting hooks passed:
git diff --checkThe mypy hook still reports the existing Windows-specific
mmaptyping errors infloss/utils.py:617; no new typing errors are introduced by this change.Companion test-data PR: mandiant/flare-floss-testfiles#27