Skip to content

fix(yara): make reverse-shell socket strings span newlines - #593

Open
udsy19 wants to merge 1 commit into
NVIDIA:mainfrom
udsy19:fix/yara-reverse-shell-multiline
Open

udsy19 wants to merge 1 commit into
NVIDIA:mainfrom
udsy19:fix/yara-reverse-shell-multiline

Conversation

@udsy19

@udsy19 udsy19 commented Sep 20, 2026

Copy link
Copy Markdown

Fixes #592

What was wrong

The built-in reverse_shell rule's $python_socket and $perl_socket strings (src/skillspector/yara_rules/malware.yar.b64, base64-packaged) use .* between the socket-family constant and the connect call with no dotall modifier:

$python_socket    = /socket\.socket\(.*SOCK_STREAM.*\.connect\(/
$perl_socket      = /use\s+Socket;.*socket\s*\(\s*SOCK/

YARA's . does not match \n by default, so both only match when the whole snippet is on one physical line — the python3 -c '...' / perl -e '...' one-liner form. A reverse shell bundled as an actual script file writes socket.socket(...) and .connect(...) as separate statements, and neither string — nor any other string in the rule — matches it. static_yara reports completed, the scan stays SAFE, --fail-on-incomplete exits 0.

Who reaches this / entry point: every skillspector scan invocation goes through the built-in reverse_shell YARA rule on every scanned artifact — this is the default, always-on detection path, not an opt-in flag. Triggered by: a skill bundle containing a Python (or Perl) source file with a raw socket-based reverse shell written as normal multi-line code, which is how this payload looks when it ships as a file rather than a one-liner.

Fix

Add s (dotall) to both strings, bounded with .{0,200} instead of unbounded .*, so the match still requires the socket call and the connect call to sit within roughly a couple of screens of each other rather than opening an any-file-wide match. Verified this bound does not introduce a false positive: an unrelated socket.socket(...)/.connect(...) pair 200 lines apart in the same file does not match.

Confirmed the regex content is unchanged since the initial release (7ced4fb) and untouched by the base64-repackaging commit (90a9181, #236) — that commit changed only the on-disk encoding, not any rule text.

Testing

$ PYTHONPATH=src .venv/bin/python -m pytest tests/nodes/analyzers/test_static_yara.py -q
87 passed

New test test_reverse_shell_rule_matches_multiline_python_socket builds a realistic multi-line Python reverse shell (base64-encoded fixture, matching this test file's existing convention of not embedding raw malware-signature strings in the source, per the module docstring) and asserts reverse_shell/YR1 fires.

Negative control, reverting only malware.yar.b64 and keeping the new test:

$ PYTHONPATH=src .venv/bin/python -m pytest tests/nodes/analyzers/test_static_yara.py::TestBuiltInMalwarePackaging::test_reverse_shell_rule_matches_multiline_python_socket -q
1 failed

restoring the fix:

$ PYTHONPATH=src .venv/bin/python -m pytest tests/nodes/analyzers/test_static_yara.py::TestBuiltInMalwarePackaging::test_reverse_shell_rule_matches_multiline_python_socket -q
1 passed

ruff check src/ tests/nodes/analyzers/test_static_yara.py and ruff format --check tests/nodes/analyzers/test_static_yara.py: all clean. (malware.yar.b64 is not Python; ruff check src/ tests/ in directory mode correctly skips it, matching how the repo's own make lint runs.)

Note on the diff: because the rule file is base64-packaged, this two-line source change renders as a full-file diff in the .b64 blob. The decoded before/after above is the actual change; nothing else in the rule file moved.

Impact: silent-wrong-result

Signed-off-by: Udaya Tejas udayatejas2004@gmail.com

$python_socket and $perl_socket in the built-in reverse_shell rule
(src/skillspector/yara_rules/malware.yar.b64) use `.*` between the
socket-family constant and the connect call with no dotall modifier.
YARA's `.` does not match `\n` by default, so the pattern only matches
when the whole snippet is on one physical line. A reverse shell
bundled as a script file (rather than passed to `python3 -c '...'` /
`perl -e '...'`) writes socket.socket(...) and .connect(...) as
separate statements and the rule never fires — reverse_shell falls
back to any of the other, unrelated strings in the same rule, so a
plain multi-line Python or Perl socket reverse shell with no other
telltale string is scored SAFE.

Add `s` (dotall) with a bounded {0,200} gap on both strings so the
match still requires the socket call and the connect call to be
close together, rather than opening an unbounded any-file-wide match
that would trade the false negative for a false positive.

Signed-off-by: Udaya Tejas <udayatejas2004@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Built-in reverse_shell YARA rule misses multi-line Python/Perl socket reverse shells

1 participant