mirror of
https://github.com/angr/angr
synced 2026-08-17 12:23:11 -04:00
CFG: Scan only the Mach-O sections that hold instructions
_executable_memory_regions took every section of an executable Mach-O segment without asking anything about the section, unlike the ELF, PE/COFF and XBE branches beside it. The whole of __TEXT is r-x, so the regions to analyze covered the constant pools, string literals, unwind tables and Objective-C and Swift metadata sitting beside the code: 73,172 bytes over eleven regions on tests/aarch64/ReverseOneSignal.app/Frameworks/OneSignalCore.framework/OneSignalCore against the 55,984 bytes in four that the file states hold instructions, and 2,195,844 against 1,699,668 on a larger dylib. Requires the cle change that makes MachOSection.is_executable answer from the section's own S_ATTR_*INSTRUCTIONS bits; against a cle without it this filter is a no-op, because every section of an r-x segment still reports as executable. Over every Mach-O in binaries the regions shrink to exactly the sections that state instructions, and the blocks that go with them are 33 that began in __unwind_info, __cstring, __objc_methname and __objc_classname, plus one on armhf/FileProtection-05.armv7.macho that began in the last word of __stub_helper and ran 194 bytes into __objc_methname. Eight fixtures gain a four-byte block at the last word of __objc_stubs, a brk #1 the merged region caused the scan to step over. No block that begins inside a section stating instructions is lost.
This commit is contained in:
parent
503b1be066
commit
cd8e13da13
2 changed files with 64 additions and 4 deletions
|
|
@ -854,11 +854,13 @@ class CFGBase(Analysis):
|
|||
# Get all executable segments
|
||||
for seg in b.segments:
|
||||
if seg.is_executable:
|
||||
# Take all sections from this segment (MachO style)
|
||||
# The whole of __TEXT is r-x, so this segment covers its constant pools and string
|
||||
# literals as well as its code.
|
||||
for section in seg.sections:
|
||||
max_mapped_addr = section.min_addr + min(section.memsize, section.filesize)
|
||||
tpl = (section.min_addr, max_mapped_addr)
|
||||
memory_regions.append(tpl)
|
||||
if section.is_executable:
|
||||
max_mapped_addr = section.min_addr + min(section.memsize, section.filesize)
|
||||
tpl = (section.min_addr, max_mapped_addr)
|
||||
memory_regions.append(tpl)
|
||||
|
||||
elif isinstance(b, (Hex, SRec)):
|
||||
if b.regions:
|
||||
|
|
|
|||
58
tests/analyses/cfg/test_cfg_macho_sections.py
Normal file
58
tests/analyses/cfg/test_cfg_macho_sections.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#!/usr/bin/env python
|
||||
# pylint: disable=missing-class-docstring,no-self-use
|
||||
from __future__ import annotations
|
||||
|
||||
__package__ = __package__ or "tests.analyses.cfg" # pylint:disable=redefined-builtin
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from archinfo.arch_arm import get_real_address_if_arm
|
||||
|
||||
import angr
|
||||
from tests.common import bin_location
|
||||
|
||||
test_location = os.path.join(bin_location, "tests")
|
||||
|
||||
|
||||
def sections_holding_blocks(project, cfg):
|
||||
"""The names of the sections the blocks CFGFast produced begin in."""
|
||||
names = set()
|
||||
for node in cfg.model.nodes():
|
||||
if node.is_simprocedure or not node.size:
|
||||
continue
|
||||
addr = get_real_address_if_arm(project.arch, node.addr)
|
||||
section = project.loader.main_object.find_section_containing(addr)
|
||||
if section is not None:
|
||||
names.add(section.name)
|
||||
return names
|
||||
|
||||
|
||||
class TestCfgMachOSections(unittest.TestCase):
|
||||
def test_cstring_and_unwind_info_are_not_scanned(self):
|
||||
bin_path = os.path.join(test_location, "x86_64", "fauxware.macho")
|
||||
project = angr.Project(bin_path, auto_load_libs=False)
|
||||
cfg = project.analyses.CFGFast()
|
||||
|
||||
text = project.loader.main_object.sections_map["__TEXT,__text"]
|
||||
assert (text.vaddr, text.vaddr + text.memsize) in cfg.regions
|
||||
for name in ("__TEXT,__cstring", "__TEXT,__unwind_info"):
|
||||
section = project.loader.main_object.sections_map[name]
|
||||
assert (section.vaddr, section.vaddr + section.memsize) not in cfg.regions
|
||||
|
||||
assert cfg.kb.functions.get_by_addr(project.entry) is not None
|
||||
assert sections_holding_blocks(project, cfg) == {"__text", "__stubs", "__stub_helper"}
|
||||
|
||||
def test_objc_string_sections_are_not_scanned(self):
|
||||
# This image places the Objective-C name tables directly after __stub_helper, so a decode that runs off
|
||||
# the end of the stubs marches through them.
|
||||
bin_path = os.path.join(test_location, "armhf", "FileProtection-05.armv7.macho")
|
||||
project = angr.Project(bin_path, auto_load_libs=False)
|
||||
cfg = project.analyses.CFGFast()
|
||||
|
||||
assert len(cfg.kb.functions) > 200
|
||||
assert sections_holding_blocks(project, cfg) == {"__text", "__stub_helper", "__symbolstub1"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue