You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
False positive: exploit_framework's $rop_chain matches every Rust impl Drop (fn drop(&mut self)) #605
exploit_framework's $rop_chain string matches Rust's destructor signature, so every impl Drop in every Rust codebase is reported as an exploit framework at HIGH severity.
src/skillspector/yara_rules/hacktools.yar:83:
$rop_chain = /ROP\s*\(.*elf\)/ nocase
The string is clearly meant for pwntools' ROP(elf). But nocase with no word boundary before ROP lets it match the interior of fn drop(&mut self):
pattern
consumes
ROP\s*\(
the tail of drop(
.*
&mut s
elf\)
self)
Since the rule's condition is any of them, this one string alone fires the whole HIGH/0.8-confidence rule.
Reproduction
A nine-line Rust file containing nothing but a textbook RAII destructor:
importyararules=yara.compile(source=r'''rule exploit_framework { strings: $rop_chain = /ROP\s*\(.*elf\)/ nocase condition: any of them}''')
forminrules.match('drop.rs'):
forsinm.strings:
forinstins.instances:
print(m.rule, s.identifier, inst.matched_data)
exploit_framework $rop_chain b'rop(&mut self)'
Impact
We run SkillSpector as a blocking security gate in CI, pinned at 939da7d41eed4282e4d8217fe2254c69f690027e. Across two Rust marketplace plugins, 14 of 14 exploit_framework hits were this, and not one was a real finding. Every one was an ordinary destructor: impl Drop for TempDir removing its own temp dir, impl Drop for Browser reaping a child process it spawned, impl Drop for KeypressSession restoring termios.
Because the finding is per-occurrence, it had already cost us twelve hand-written waivers, and four of those had gone stale as the destructors moved. That is what prompted digging into the rule itself rather than waiving occurrence number thirteen.
This is the same shape as #487 (php_webshell_known firing on the German word "behindert"): a nocase substring with no word boundary matching inside an unrelated word.
Suggested fix
A word boundary is enough, and keeps nocase:
$rop_chain = /\bROP\s*\(.*elf\)/ nocase
\b fails inside drop( because the preceding d is a word character. Dropping nocase for this string would also work, since pwntools is always written ROP(...), and would be the more faithful reading of the intent.
Worth noting separately that .* is unanchored here, so $rop_chain can also span a long stretch of unrelated text between a ROP( and a later elf). Tightening it to something like [^\n)]* would narrow that, though the word boundary alone resolves the case reported here.
Happy to send a PR with the fix and a regression fixture if that is useful.
Confirmed fixed, thanks. Verified independently against yara-python: the 9-line Rust impl Drop repro no longer matches under /\bROP\s*\(.*elf\)/, and ROP(elf) still does, so the precision gain costs no detection.
For context on the impact, this rule accounted for 14 of 14 exploit_framework hits in our scan of two Rust workspaces, none of them real, and had cost us twelve hand-written waivers before we traced it to the rule itself.
Appreciate the quick turnaround and the regression tests.
Activity
fix(yara): require word boundary on exploit_framework $rop_chain (NVI…
dalekurt commentedon Sep 23, 2026
Confirmed fixed, thanks. Verified independently against yara-python: the 9-line Rust
impl Droprepro no longer matches under/\bROP\s*\(.*elf\)/, andROP(elf)still does, so the precision gain costs no detection.For context on the impact, this rule accounted for 14 of 14
exploit_frameworkhits in our scan of two Rust workspaces, none of them real, and had cost us twelve hand-written waivers before we traced it to the rule itself.Appreciate the quick turnaround and the regression tests.