import unittest from rpcscope.parsers import parse_enum4linux, parse_nxc, parse_rpcdump class ParserTests(unittest.TestCase): def test_nxc_alive_evidence_requires_a_banner_or_positive_response(self): hosts = parse_nxc("\n".join([ "SMB 192.0.2.1 445 NONE [-] Error connecting to host: timed out", "SMB 192.0.2.2 445 HOST [*] Windows 11 (name:HOST)", "SMB 192.0.2.3 445 HOST [+] LAB\\auditor:password", ])) self.assertEqual(hosts["192.0.2.1"].services, {}) self.assertEqual(hosts["192.0.2.2"].services, {"445": "open"}) self.assertEqual(hosts["192.0.2.3"].services, {"445": "open"}) def test_parse_nxc_host_and_share(self): text = """ SMB 10.10.10.25 445 FILE01 [*] Windows 7 Build 7601 (name:FILE01) (domain:LAB) (signing:False) (SMBv1:True) SMB 10.10.10.25 445 FILE01 [+] LAB\\guest SMB 10.10.10.25 445 FILE01 Public READ,WRITE Team share """ host = parse_nxc(text)["10.10.10.25"] self.assertEqual(host.hostname, "FILE01") self.assertIs(host.smb1, True) self.assertIs(host.signing, False) self.assertIs(host.guest_session, True) self.assertEqual(host.shares[0].name, "Public") self.assertEqual(host.shares[0].access, ["READ", "WRITE"]) def test_parse_rpcdump_block(self): text = """ Protocol: [MS-RPRN]: Print System Remote Protocol Provider: spoolsv.exe UUID : 12345678-1234-ABCD-EF00-0123456789AB v1.0 Bindings: ncacn_np:10.10.10.25[\\pipe\\spoolss] """ host = parse_rpcdump(text, "10.10.10.25") self.assertEqual(host.rpc_interfaces[0].uuid, "12345678-1234-abcd-ef00-0123456789ab") self.assertIn("spoolss", host.rpc_interfaces[0].endpoint) def test_parse_enum4linux_core_fields(self): data = { "target": {"host": "10.10.10.25"}, "sessions": {"sessions_possible": True, "null": True, "guest": False}, "smb": {"smb1_supported": True, "signing_required": False}, "shares": {"Public": {"access": {"mapping": "OK", "listing": "OK"}}}, } host = parse_enum4linux(data, "fixture.json") self.assertEqual(host.address, "10.10.10.25") self.assertIs(host.null_session, True) self.assertIs(host.smb1, True) self.assertIs(host.signing, False) self.assertEqual(host.shares[0].access, ["READ"]) def test_enum4linux_current_schema_and_server_facts(self): data = { "target": {"host": "10.10.10.25"}, "credentials": {"domain": "LOGIN-DOMAIN", "password": "WRITE"}, "smb_dialects": { "Supported dialects": {"SMB 1.0": False, "SMB 3.1.1": True}, "SMB1 only": False, "SMB signing required": True, }, "smb_domain_info": {"NetBIOS computer name": "FILE01", "NetBIOS domain name": "LAB"}, "rpc_domain_info": {"Domain": "RPC-LAB"}, "os_info": {"OS": "Windows Server 2019", "Native OS": "Windows 10.0"}, } host = parse_enum4linux(data, "fixture.json") self.assertIs(host.smb1, False) self.assertIs(host.signing, True) self.assertEqual(host.hostname, "FILE01") self.assertEqual(host.domain, "RPC-LAB") self.assertEqual(host.os, "Windows Server 2019") def test_enum_share_requires_successful_listing(self): data = { "target": {"host": "10.10.10.25"}, "shares": { "Denied": {"access": {"mapping": "ok", "listing": "denied"}}, "WrongPassword": {"access": {"mapping": "ok", "listing": "wrong password"}}, "IPC$": {"access": {"mapping": "ok", "listing": "not supported"}}, "NoProbe": {"comment": "READ WRITE backup share"}, "FalseFlags": {"access": {"read": False, "write": False}}, "Readable": {"access": {"mapping": "ok", "listing": "ok"}}, }, } host = parse_enum4linux(data, "fixture.json") permissions = {share.name: share.access for share in host.shares} self.assertEqual(permissions.pop("Readable"), ["READ"]) self.assertTrue(all(access == [] for access in permissions.values())) def test_enum_ignores_client_settings_and_credentials(self): host = parse_enum4linux({"target": {"host": "10.10.10.25"}, "credentials": {"domain": "ATTACKER"}, "smb": {"ipc_signing": False}, "errors": {"os": "failed"}}, "fixture.json") self.assertIsNone(host.signing) self.assertEqual(host.domain, "") self.assertEqual(host.os, "") def test_nxc_does_not_infer_null_or_guest_from_partial_names(self): text = """ SMB 10.10.10.25 445 FILE01 [+] LAB\\annulled:anonymous SMB 10.10.10.25 445 FILE01 [+] LAB\\guestadmin:password SMB 10.10.10.25 445 FILE01 [-] guest session failed """ host = parse_nxc(text)["10.10.10.25"] self.assertIsNone(host.null_session) self.assertIsNone(host.guest_session) def test_nxc_explicit_null_and_guest_flags(self): text = """ SMB 10.10.10.25 445 FILE01 [*] Windows Server 2019 (name:FILE01) (domain:LAB) (Null Auth:True) (Guest Auth:True) """ host = parse_nxc(text)["10.10.10.25"] self.assertIs(host.null_session, True) self.assertIs(host.guest_session, True) def test_nxc_handles_ipv6_and_repeated_share_permissions(self): text = """ SMB 2001:db8::25 445 FILE01 [*] Windows Server 2019 (name:FILE01) (domain:LAB) SMB 2001:db8::25 445 FILE01 Data READ first pass SMB 2001:db8::25 445 FILE01 data READ,WRITE second pass SMB 2001:db8::25 445 FILE01 Team Files READ Shared files """ host = parse_nxc(text)["2001:db8::25"] self.assertEqual(len(host.shares), 2) self.assertEqual(host.shares[0].access, ["READ", "WRITE"]) self.assertEqual(host.shares[1].name, "Team Files") def test_nxc_does_not_parse_credential_or_comment_as_share(self): text = """ SMB 10.10.10.25 445 FILE01 [+] LAB\\user:READ WRITE SMB 10.10.10.25 445 FILE01 Public READONLY This is a comment """ self.assertEqual(parse_nxc(text)["10.10.10.25"].shares, []) def test_nxc_share_table_keeps_acl_labels_and_ignores_denied_remarks(self): prefix = "SMB 10.10.10.25 445 FILE01 " rows = [f"{'Share':<15} {'Permissions':<22} Remark", f"{'Denied':<15} {'':<22} READ WRITE access is denied", f"{'AclOnly':<15} {'READ,WRITE (ACL)':<22} Permission changes allowed", f"{'SubdirOnly':<15} {'WRITE (SUBDIR)':<22} Directory creation allowed", f"{'A very long share name':<15} {'READ':<22} Long name"] host = parse_nxc("\n".join(prefix + row for row in rows))["10.10.10.25"] self.assertEqual([share.name for share in host.shares], ["AclOnly", "SubdirOnly", "A very long share name"]) self.assertEqual(host.shares[0].access, ["READ", "WRITE (ACL)"]) self.assertEqual(host.shares[1].access, ["WRITE (SUBDIR)"]) def test_rpcdump_preserves_all_bindings_and_inline_annotation(self): text = """ Protocol: [MS-RPRN]: Print System Remote Protocol Provider: spoolsv.exe UUID : 12345678-1234-ABCD-EF00-0123456789AB v1.0 Print annotation Bindings: ncalrpc:[local-spooler] ncacn_ip_tcp:10.10.10.25[49665] ncacn_np:10.10.10.25[\\pipe\\spoolss] ncacn_np:10.10.10.25[\\pipe\\spoolss] """ interface = parse_rpcdump(text, "10.10.10.25").rpc_interfaces[0] self.assertEqual(len(interface.bindings), 3) self.assertEqual(interface.endpoint, "ncalrpc:[local-spooler]") self.assertEqual(interface.annotation, "Print annotation") def test_rpcdump_unlabelled_uuid_in_mixed_output(self): text = """ UUID: 12345678-1234-abcd-ef00-0123456789ab v1.0 Bindings: ncacn_ip_tcp:10.10.10.25[49665] 367abb81-9844-35f1-ad32-98f038001003 2.0 Service manager Bindings: ncacn_np:10.10.10.25[\\pipe\\svcctl] """ interfaces = parse_rpcdump(text, "10.10.10.25").rpc_interfaces self.assertEqual(len(interfaces), 2) self.assertEqual(interfaces[1].version, "2.0") self.assertEqual(interfaces[1].annotation, "Service manager") if __name__ == "__main__": unittest.main()