Skip to content

Commit 0f01c85

Browse files
authored
Merge pull request #134 from DarkaMaul/dm/fix-115
Fix #115: export hidden segments (e.g. .reloc) to avoid scan crash
2 parents 9220e37 + 0a71fef commit 0f01c85

4 files changed

Lines changed: 77 additions & 4 deletions

File tree

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ tests/dataset/puraUpdate.quokka filter=lfs diff=lfs merge=lfs -text
1111
tests/dataset/puraUpdate_ghidra.quokka filter=lfs diff=lfs merge=lfs -text
1212
tests/dataset/sig_test filter=lfs diff=lfs merge=lfs -text
1313
tests/dataset/nf_nat_pt.ko filter=lfs diff=lfs merge=lfs -text
14+
tests/dataset/StatusCodeHandlerPei.efi filter=lfs diff=lfs merge=lfs -text

src/Segment.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,12 @@ int ExportSegments() {
140140

141141
segment_t* seg = get_first_seg();
142142
while (seg != nullptr) {
143-
// A HEADER segment is considered ephemeral even though instructions might
144-
// reference it. See https://github.com/quarkslab/quokka/issues/29
145-
if (seg->is_header_segm() ||
146-
(is_visible_segm(seg) && !is_ephemeral_segm(seg->start_ea))) {
143+
// Skip only ephemeral (transient debugger) segments. Hidden segments such
144+
// as HEADER (issue #29) and .reloc (issue #115) are still mapped and may be
145+
// referenced by the analysis, so IDA creates data/instructions inside them.
146+
// Excluding them makes the linear scan crash with "doesn't belong to any
147+
// segment", so keep every non-ephemeral segment regardless of visibility.
148+
if (!is_ephemeral_segm(seg->start_ea)) {
147149
segments.emplace(seg->sel, seg);
148150
}
149151

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:18bd0e3e61f92e8cd0eae8c5873e95549ec49287974fe11ac8a9b2734d9f651a
3+
size 1184

tests/python/tests/ida/test_ida_export.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,3 +1584,70 @@ def test_decomp_inport_is_word_array_300(self):
15841584
elem = data.type.element_type
15851585
assert isinstance(elem, BaseType)
15861586
assert elem == BaseType.WORD
1587+
1588+
1589+
# ---------------------------------------------------------------------------
1590+
# StatusCodeHandlerPei.efi regression: hidden .reloc segment (issue #115)
1591+
# ---------------------------------------------------------------------------
1592+
1593+
1594+
@requires_ida
1595+
class TestRelocSegmentExport:
1596+
"""Export a PE/EFI binary whose ``.reloc`` segment is hidden by IDA.
1597+
1598+
Regression test for https://github.com/quarkslab/quokka/issues/115.
1599+
1600+
IDA marks the ``.reloc`` segment as hidden (``SFL_HIDDEN``) but still
1601+
creates a data item inside it (referenced from the PE header). Before the
1602+
fix, ``ExportSegments`` skipped every non-visible segment, so the linear
1603+
scan reached that data item, ``getseg`` returned a segment absent from
1604+
Quokka's collection, and the export aborted with
1605+
``Data at address 0x... doesn't belong to any segment``.
1606+
1607+
Simply reaching the assertions below means ``from_binary`` returned instead
1608+
of raising ``QuokkaError`` on the abort, i.e. the crash no longer happens.
1609+
"""
1610+
1611+
# Start of the .reloc segment in this sample; the crashing data item.
1612+
RELOC_START = 0x480
1613+
1614+
@pytest.fixture(autouse=True)
1615+
def _export(self, root_directory: Path, tmp_path: Path):
1616+
"""Export StatusCodeHandlerPei.efi through IDA into a temp directory."""
1617+
binary = root_directory / "tests" / "dataset" / "StatusCodeHandlerPei.efi"
1618+
if not binary.exists():
1619+
pytest.skip("StatusCodeHandlerPei.efi not found in tests/dataset/")
1620+
1621+
output = tmp_path / "StatusCodeHandlerPei.quokka"
1622+
self.prog = quokka.Program.from_binary(
1623+
binary,
1624+
output_file=output,
1625+
database_file=tmp_path / "StatusCodeHandlerPei.i64",
1626+
timeout=600,
1627+
)
1628+
1629+
def test_export_produces_program(self):
1630+
"""Export completes without aborting on the hidden .reloc segment."""
1631+
assert self.prog is not None
1632+
1633+
def test_reloc_segment_is_exported(self):
1634+
"""The hidden .reloc segment is present in the export."""
1635+
# IDA exports the segment under its visible name ("_reloc").
1636+
segments = list(self.prog.segments.values())
1637+
reloc = [s for s in segments if s.start == self.RELOC_START]
1638+
assert len(reloc) == 1, (
1639+
f"Expected exactly one segment starting at 0x{self.RELOC_START:x}, "
1640+
f"got {[(s.name, hex(s.start)) for s in segments]}"
1641+
)
1642+
assert reloc[0].name == "_reloc"
1643+
1644+
def test_reloc_data_belongs_to_a_segment(self):
1645+
"""The data item that used to crash the scan resolves to a segment."""
1646+
covering = [
1647+
s for s in self.prog.segments.values() if s.in_segment(self.RELOC_START)
1648+
]
1649+
assert len(covering) == 1, (
1650+
f"Address 0x{self.RELOC_START:x} should belong to exactly one segment, "
1651+
f"got {len(covering)}"
1652+
)
1653+
assert self.RELOC_START in self.prog.data

0 commit comments

Comments
 (0)