Skip to content

v4.11.0

Choose a tag to compare

@github-actions github-actions released this 15 Oct 20:50
· 24 commits to master since this release
bff1bfd

Added

  • (utility) extract_strings reads Go and Rust strings at their real length, from the paired length load or a
    (pointer, length) header, as UTF-8, with data_addr on the string. Adds mode="rust", picked when the Rust score
    and a /rustc/<commit>/ path agree; Go mode still needs a pclntab. A reference without a usable length keeps the
    NUL-terminated read, stopped at the next referenced address. Measured on 0575a23, reproduced by reviewer on
    bundled fixtures:
    rust_pe_gnu 2657 -> 2909 refs, strings over 200 chars 820 -> 1, 3 pointer reads lost. Cost:
    an in-image /rustc/ path puts mirai_x64 in Rust mode and shortens 13 of its 162 strings, 16 before #402 (tail-merged C strings
    such as "\r\n" -> "\r"); string extraction on rust_pe_gnu went 0.66 s -> 1.12 s on the reviewer's machine. (#382)

Changed

  • (core) Keep the per-instruction state of large images compact: a lone code reference is stored bare, not in a
    one-element set (AddressRefs), code_map reads ins2fn, mnemonics are interned and pointer escaping drops the
    capstone detail. Measured on af37541, not bundled: RSS after analysis libjvm.so 7,289 to 4,015 MB. Reproduced
    by reviewer:
    peak RSS -26 to -35% on libpython3.14.so, libcrypto.so.3 and an x64 and an ARM64 PE, CPU 1-7% lower,
    reports identical. Cost: code_map can no longer be assigned, writes through it land in ins2fn, and its values
    are function starts; lone code_refs_from/code_refs_to entries read as frozensets that only add() and
    discard() change. (#396)
  • (core) Suspend the cyclic garbage collector while a buffer is analysed. Analysis builds millions of long-lived
    objects and almost no cycles, so every full collection walked the growing heap and freed nothing. Measured on
    af37541, not bundled, reproduced by reviewer:
    analysis CPU on libpython -27% under CPython 3.12 (14.4 to 10.5 s),
    -5 to -7% under 3.14, whose incremental collector already does less; reports identical. Cost: the collector is off
    process-wide during analysis, and a toDict() right after pays one heap walk (1.0 to 2.2 s on 3.12). (#396)

Fixed

  • (loaders) Start a mapped ELF at its PT_LOAD segments where the sections miss them, so the ELF header is
    mapped. With no section headers (as UPX leaves them) the base was 0; with a header-only first PT_LOAD (lld, no
    .rodata or build-id) it sat a page above the header. Either way disassembleBuffer lost the header and found no
    code or the wrong architecture. Measured on b34154e: the 18 bundled ELF fixtures with section headers zeroed
    map at their sectioned base and size, was 3 (three overran MAX_IMAGE_SIZE). Reproduced on bundled fixtures. No
    cost: base and functions unchanged on the shipped fixtures, 264 Debian armhf/armel and 50 x86 system ELFs. (#394)
  • (loaders) Keep a mapped ELF buffer's dynamic symbols. lief read disassembleBuffer()'s image by file offset and
    so missed the dynamic table of any segment mapped above its offset; ElfFileLoader.unmapImage now rebuilds the file
    layout from the PT_LOADs when the table is found at its address but not at its offset. Measured on af37541: 7
    built x86/x64/AArch64 libraries, exported-function labels in buffer mode 0 -> 33 (file 33); the ELF fixtures gain
    their imports in buffer mode (19, 16, 3), functions/blocks/instructions bit-identical on every pre-existing fixture
    in every mode. Reproduced on bundled fixtures. Cost: one extra lief parse per mapped ELF buffer. (#395)
  • (intel) Keep the body behind an early ret in its function when the body is analyzed first. A body whose
    prologue outranked its entry in the candidate order was a function by the time the entry's jcc reached it; the jcc
    now takes it back when the entry's own code runs without a gap into the ret in front of it, at most 0x100 bytes
    past the entry, and analyzes it again if the entry does not keep it. Reproduced by reviewer on 494 benign PEs (not
    bundled):
    70 starts fewer in 55 files, all bodies behind their entry's ret (_RTC_CheckEsp, __isa_available
    helpers, LLVM shrink-wrapped functions), runtime unchanged. (#388, #389)
  • (utility) Go mode no longer loses the whole report to an OverflowError when a string header's length word is
    out of range; sized reads stop at 64 KiB. Lengths that cut a C string short now fall back to it, so cgo code keeps
    open("/dev/watchdog", 2) whole. Bundled fixtures with a pclntab header planted in padding: mirai_x64 errored
    on master and now has 125 refs (162 unplanted); in Go mode 1-character reads are dropped. go1.24.7 amd64 and 386
    builds, not bundled, not reproduced:
    unchanged by the fallback, 4 two-character reads gained on 386. (#382)
  • (utility) Go and Rust mode keep a C string whose last character the compiler also stores as a string of its
    own ("=" in the tail of "url="); the NUL-terminated read stopped at the suffix's reference. Adds a 1 MB go1.24.7
    linux/amd64 fixture so Go mode is tested on a real pclntab. Measured on 1cec0bc, reproduced by reviewer on
    bundled fixtures:
    mirai_x64 with a planted pclntab header or /rustc/ path keeps url= and Cookie: (3 refs
    longer; in Rust mode 13 strings shortened against unplanted, was 16); rust_pe_gnu and the Go fixture are
    unchanged. Chains of suffixes ("\r\n\r\n", "\r\n", "\n") are still cut. (#402)
  • (ida) The IDAPython backend no longer fails the whole export when IDA passes no name for an import by ordinal.
    _cbEnumImports concatenated the missing name and raised TypeError; it now writes module!#<ordinal>, as the
    ida_domain backend already does. A stubbed enum_import_names test reproduces it. IDA 9.4 names such an import
    itself (mcritord_23), so it is not known which IDA versions reach the error. No recovery output changes. (#400)

Compatibility

  • (intel) x86 function boundaries move where an early-return body was analyzed before its entry: the body now joins
    the entry instead of standing as a function of its own. (#388, #389)
  • (utility) String output changes on every binary extracted in Go or Rust mode: strings stop at their real
    length instead of running into the next literal, some are added or dropped, and UTF-8 ones carry the new
    utf8 type. Nothing in mcrit, mcrit-plugin or capa reads type or data_addr. (#382)

What's Changed

Changes

  • fix(loaders): take the ELF base from PT_LOAD when the sections miss the header by @r0ny123 in #394
  • fix(loaders): read a mapped ELF buffer's dynamic symbols through its … by @r0ny123 in #395
  • perf(core): keep large images' per-instruction state compact by @r0ny123 in #396
  • fix(intel): keep an early-return body in its function when it is analyzed first by @r0ny123 in #389
  • feat(utility): read Go and Rust strings at their real length by @r0ny123 in #382
  • ci: move the dangerous-triggers ignore onto the trigger line by @danielplohmann in #399
  • fix(ida): name an import by ordinal when IDAPython passes no name by @danielplohmann in #400
  • ci: allow the arm title scope by @r0ny123 in #401
  • fix(utility): keep tail-merged C strings in Go and Rust mode by @r0ny123 in #402
  • build: release 4.11.0 by @danielplohmann in #403

Dependency and tooling updates

Full Changelog: v4.10.0...v4.11.0