Merge pull request #2358 from guardicore/2268-modify-ping-scanner-with-agent-queue

2268 modify ping scanner with agent queue
This commit is contained in:
Mike Salvatore 2022-09-29 07:57:29 -04:00 committed by GitHub
commit 5a0251c442
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 245 additions and 48 deletions

View File

@ -1,4 +1,7 @@
from common.types import PingScanData from ipaddress import IPv4Address
from typing import Optional
from common import OperatingSystem
from . import AbstractAgentEvent from . import AbstractAgentEvent
@ -8,7 +11,10 @@ class PingScanEvent(AbstractAgentEvent):
An event that occurs when the agent performs a ping scan on its network An event that occurs when the agent performs a ping scan on its network
Attributes: Attributes:
:param scan_data: The data collected from the ping scan :param response_received: Is any response from ping recieved
:param os: Operating system from the target system
""" """
scan_data: PingScanData target: IPv4Address
response_received: bool
os: Optional[OperatingSystem]

View File

@ -5,7 +5,7 @@ import subprocess
import sys import sys
from ipaddress import IPv4Interface from ipaddress import IPv4Interface
from pathlib import Path, WindowsPath from pathlib import Path, WindowsPath
from typing import List, Optional, Tuple from typing import List, Optional, Sequence, Tuple
from pubsub.core import Publisher from pubsub.core import Publisher
@ -258,11 +258,6 @@ class InfectionMonkey:
return agent_event_serializer_registry return agent_event_serializer_registry
def _build_server_list(self, relay_port: int):
my_servers = map(str, self._opts.servers)
relay_servers = [f"{ip}:{relay_port}" for ip in get_my_ip_addresses()]
return my_servers + relay_servers
def _build_master(self, relay_port: int): def _build_master(self, relay_port: int):
servers = self._build_server_list(relay_port) servers = self._build_server_list(relay_port)
local_network_interfaces = get_network_interfaces() local_network_interfaces = get_network_interfaces()
@ -272,14 +267,14 @@ class InfectionMonkey:
self._control_channel self._control_channel
) )
event_queue = PyPubSubAgentEventQueue(Publisher()) agent_event_queue = PyPubSubAgentEventQueue(Publisher())
self._subscribe_events( self._subscribe_events(
event_queue, agent_event_queue,
propagation_credentials_repository, propagation_credentials_repository,
self._agent_event_serializer_registry, self._agent_event_serializer_registry,
) )
puppet = self._build_puppet(event_queue) puppet = self._build_puppet(agent_event_queue)
victim_host_factory = self._build_victim_host_factory(local_network_interfaces) victim_host_factory = self._build_victim_host_factory(local_network_interfaces)
@ -298,36 +293,41 @@ class InfectionMonkey:
propagation_credentials_repository, propagation_credentials_repository,
) )
def _build_server_list(self, relay_port: int) -> Sequence[str]:
my_servers = set(map(str, self._opts.servers))
relay_servers = {f"{ip}:{relay_port}" for ip in get_my_ip_addresses()}
return list(my_servers.union(relay_servers))
def _subscribe_events( def _subscribe_events(
self, self,
event_queue: IAgentEventQueue, agent_event_queue: IAgentEventQueue,
propagation_credentials_repository: IPropagationCredentialsRepository, propagation_credentials_repository: IPropagationCredentialsRepository,
agent_event_serializer_registry: AgentEventSerializerRegistry, agent_event_serializer_registry: AgentEventSerializerRegistry,
): ):
event_queue.subscribe_type( agent_event_queue.subscribe_type(
CredentialsStolenEvent, CredentialsStolenEvent,
add_credentials_from_event_to_propagation_credentials_repository( add_credentials_from_event_to_propagation_credentials_repository(
propagation_credentials_repository propagation_credentials_repository
), ),
) )
event_queue.subscribe_all_events( agent_event_queue.subscribe_all_events(
AgentEventForwarder(self._island_api_client, agent_event_serializer_registry).send_event AgentEventForwarder(self._island_api_client, agent_event_serializer_registry).send_event
) )
def _build_puppet( def _build_puppet(
self, self,
event_queue: IAgentEventQueue, agent_event_queue: IAgentEventQueue,
) -> IPuppet: ) -> IPuppet:
puppet = Puppet() puppet = Puppet(agent_event_queue)
puppet.load_plugin( puppet.load_plugin(
"MimikatzCollector", "MimikatzCollector",
MimikatzCredentialCollector(event_queue), MimikatzCredentialCollector(agent_event_queue),
PluginType.CREDENTIAL_COLLECTOR, PluginType.CREDENTIAL_COLLECTOR,
) )
puppet.load_plugin( puppet.load_plugin(
"SSHCollector", "SSHCollector",
SSHCredentialCollector(self._telemetry_messenger, event_queue), SSHCredentialCollector(self._telemetry_messenger, agent_event_queue),
PluginType.CREDENTIAL_COLLECTOR, PluginType.CREDENTIAL_COLLECTOR,
) )
@ -341,7 +341,7 @@ class InfectionMonkey:
island_api_client=self._island_api_client, island_api_client=self._island_api_client,
) )
exploit_wrapper = ExploiterWrapper( exploit_wrapper = ExploiterWrapper(
self._telemetry_messenger, event_queue, agent_binary_repository self._telemetry_messenger, agent_event_queue, agent_binary_repository
) )
puppet.load_plugin( puppet.load_plugin(

View File

@ -4,10 +4,16 @@ import os
import re import re
import subprocess import subprocess
import sys import sys
from ipaddress import IPv4Address
from time import time
from typing import Tuple
from common import OperatingSystem from common import OperatingSystem
from common.agent_events import PingScanEvent
from common.event_queue import IAgentEventQueue
from common.types import PingScanData from common.types import PingScanData
from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.environment import is_windows_os
from infection_monkey.utils.ids import get_agent_id
TTL_REGEX = re.compile(r"TTL=([0-9]+)\b", re.IGNORECASE) TTL_REGEX = re.compile(r"TTL=([0-9]+)\b", re.IGNORECASE)
LINUX_TTL = 64 # Windows TTL is 128 LINUX_TTL = 64 # Windows TTL is 128
@ -17,27 +23,30 @@ EMPTY_PING_SCAN = PingScanData(False, None)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def ping(host: str, timeout: float) -> PingScanData: def ping(host: str, timeout: float, agent_event_queue: IAgentEventQueue) -> PingScanData:
try: try:
return _ping(host, timeout) return _ping(host, timeout, agent_event_queue)
except Exception: except Exception:
logger.exception("Unhandled exception occurred while running ping") logger.exception("Unhandled exception occurred while running ping")
return EMPTY_PING_SCAN return EMPTY_PING_SCAN
def _ping(host: str, timeout: float) -> PingScanData: def _ping(host: str, timeout: float, agent_event_queue: IAgentEventQueue) -> PingScanData:
if is_windows_os(): if is_windows_os():
timeout = math.floor(timeout * 1000) timeout = math.floor(timeout * 1000)
ping_command_output = _run_ping_command(host, timeout) event_timestamp, ping_command_output = _run_ping_command(host, timeout)
ping_scan_data = _process_ping_command_output(ping_command_output) ping_scan_data = _process_ping_command_output(ping_command_output)
logger.debug(f"{host} - {ping_scan_data}") logger.debug(f"{host} - {ping_scan_data}")
ping_scan_event = _generate_ping_scan_event(host, ping_scan_data, event_timestamp)
agent_event_queue.publish(ping_scan_event)
return ping_scan_data return ping_scan_data
def _run_ping_command(host: str, timeout: float) -> str: def _run_ping_command(host: str, timeout: float) -> Tuple[float, str]:
ping_cmd = _build_ping_command(host, timeout) ping_cmd = _build_ping_command(host, timeout)
logger.debug(f"Running ping command: {' '.join(ping_cmd)}") logger.debug(f"Running ping command: {' '.join(ping_cmd)}")
@ -45,6 +54,8 @@ def _run_ping_command(host: str, timeout: float) -> str:
# of os.device_encoding(1) will be None. Setting errors="backslashreplace" prevents a crash # of os.device_encoding(1) will be None. Setting errors="backslashreplace" prevents a crash
# in this case. See #1175 and #1403 for more information. # in this case. See #1175 and #1403 for more information.
encoding = os.device_encoding(1) encoding = os.device_encoding(1)
ping_event_timestamp = time()
sub_proc = subprocess.Popen( sub_proc = subprocess.Popen(
ping_cmd, ping_cmd,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -64,9 +75,9 @@ def _run_ping_command(host: str, timeout: float) -> str:
logger.debug(output) logger.debug(output)
except subprocess.TimeoutExpired as te: except subprocess.TimeoutExpired as te:
logger.error(te) logger.error(te)
return "" return ping_event_timestamp, ""
return output return ping_event_timestamp, output
def _process_ping_command_output(ping_command_output: str) -> PingScanData: def _process_ping_command_output(ping_command_output: str) -> PingScanData:
@ -90,3 +101,15 @@ def _build_ping_command(host: str, timeout: float):
# on older version of ping the timeout must be an integer, thus we use ceil # on older version of ping the timeout must be an integer, thus we use ceil
return ["ping", ping_count_flag, "1", ping_timeout_flag, str(math.ceil(timeout)), host] return ["ping", ping_count_flag, "1", ping_timeout_flag, str(math.ceil(timeout)), host]
def _generate_ping_scan_event(
host: str, ping_scan_data: PingScanData, event_timestamp: float
) -> PingScanEvent:
return PingScanEvent(
source=get_agent_id(),
target=IPv4Address(host),
timestamp=event_timestamp,
response_received=ping_scan_data.response_received,
os=ping_scan_data.os,
)

View File

@ -4,6 +4,7 @@ from typing import Dict, Iterable, Sequence
from common.common_consts.timeouts import CONNECTION_TIMEOUT from common.common_consts.timeouts import CONNECTION_TIMEOUT
from common.credentials import Credentials from common.credentials import Credentials
from common.event_queue import IAgentEventQueue
from common.types import PingScanData from common.types import PingScanData
from infection_monkey import network_scanning from infection_monkey import network_scanning
from infection_monkey.i_puppet import ( from infection_monkey.i_puppet import (
@ -24,8 +25,9 @@ logger = logging.getLogger()
class Puppet(IPuppet): class Puppet(IPuppet):
def __init__(self) -> None: def __init__(self, agent_event_queue: IAgentEventQueue) -> None:
self._plugin_registry = PluginRegistry() self._plugin_registry = PluginRegistry()
self._agent_event_queue = agent_event_queue
def load_plugin(self, plugin_name: str, plugin: object, plugin_type: PluginType) -> None: def load_plugin(self, plugin_name: str, plugin: object, plugin_type: PluginType) -> None:
self._plugin_registry.load_plugin(plugin_name, plugin, plugin_type) self._plugin_registry.load_plugin(plugin_name, plugin, plugin_type)
@ -41,7 +43,7 @@ class Puppet(IPuppet):
return pba.run(options) return pba.run(options)
def ping(self, host: str, timeout: float = CONNECTION_TIMEOUT) -> PingScanData: def ping(self, host: str, timeout: float = CONNECTION_TIMEOUT) -> PingScanData:
return network_scanning.ping(host, timeout) return network_scanning.ping(host, timeout, self._agent_event_queue)
def scan_tcp_ports( def scan_tcp_ports(
self, host: str, ports: Sequence[int], timeout: float = CONNECTION_TIMEOUT self, host: str, ports: Sequence[int], timeout: float = CONNECTION_TIMEOUT

View File

@ -0,0 +1,103 @@
from ipaddress import IPv4Address
from uuid import UUID
import pytest
from tests.unit_tests.monkey_island.cc.models.test_agent import AGENT_ID
from common import OperatingSystem
from common.agent_events import PingScanEvent
PING_EVENT = PingScanEvent(
source=AGENT_ID,
target=IPv4Address("1.1.1.1"),
timestamp=1664371327.4067292,
response_received=True,
os=OperatingSystem.LINUX,
)
PING_OBJECT_DICT = {
"source": UUID("012e7238-7b81-4108-8c7f-0787bc3f3c10"),
"target": IPv4Address("1.1.1.1"),
"timestamp": 1664371327.4067292,
"tags": frozenset(),
"response_received": True,
"os": OperatingSystem.LINUX,
}
PING_SIMPLE_DICT = {
"source": "012e7238-7b81-4108-8c7f-0787bc3f3c10",
"target": "1.1.1.1",
"timestamp": 1664371327.4067292,
"tags": [],
"response_received": True,
"os": "linux",
}
def test_constructor():
assert PingScanEvent(**PING_OBJECT_DICT) == PING_EVENT
def test_from_dict():
assert PingScanEvent(**PING_SIMPLE_DICT) == PING_EVENT
def test_to_dict():
ping_scan_event = PingScanEvent(**PING_OBJECT_DICT)
assert ping_scan_event.dict(simplify=True) == PING_SIMPLE_DICT
@pytest.mark.parametrize(
"key, value",
[
("source", "not-an-uuid"),
("source", -1),
("timestamp", "not-a-timestamp"),
("response_received", "not-a-bool"),
("os", 2.1),
("os", "bsd"),
],
)
def test_construct_invalid_field__type_error(key, value):
invalid_type_dict = PING_SIMPLE_DICT.copy()
invalid_type_dict[key] = value
with pytest.raises(TypeError):
PingScanEvent(**invalid_type_dict)
@pytest.mark.parametrize(
"key, value",
[
("target", "not-a-IPv4Address"),
],
)
def test_construct_invalid_field__value_error(key, value):
invalid_type_dict = PING_SIMPLE_DICT.copy()
invalid_type_dict[key] = value
with pytest.raises(ValueError):
PingScanEvent(**invalid_type_dict)
def test_optional_os_field():
none_field_dict = PING_SIMPLE_DICT.copy()
none_field_dict["os"] = None
# Raises exception_on_failure
PingScanEvent(**none_field_dict)
def test_construct__extra_fields_forbidden():
extra_field_dict = PING_SIMPLE_DICT.copy()
extra_field_dict["extra_field"] = 99 # red balloons
with pytest.raises(ValueError):
PingScanEvent(**extra_field_dict)
def test_ping_scan_event_deserialization_dict():
serialized_event = PING_EVENT.dict()
deserialized_event = PingScanEvent(**serialized_event)
assert deserialized_event == PING_EVENT

View File

@ -4,9 +4,14 @@ from unittest.mock import MagicMock
import pytest import pytest
import infection_monkey.network_scanning.ping_scanner # noqa: F401
from common import OperatingSystem from common import OperatingSystem
from common.agent_events import PingScanEvent
from common.event_queue import IAgentEventQueue
from common.types import PingScanData
from infection_monkey.network_scanning import ping from infection_monkey.network_scanning import ping
from infection_monkey.network_scanning.ping_scanner import EMPTY_PING_SCAN from infection_monkey.network_scanning.ping_scanner import EMPTY_PING_SCAN
from infection_monkey.utils.ids import get_agent_id
LINUX_SUCCESS_OUTPUT = """ LINUX_SUCCESS_OUTPUT = """
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
@ -50,6 +55,14 @@ ttl=2d2!
""" """
TIMESTAMP = 123.321
@pytest.fixture(autouse=True)
def patch_timestamp(monkeypatch):
monkeypatch.setattr("infection_monkey.network_scanning.ping_scanner.time", lambda: TIMESTAMP)
@pytest.fixture @pytest.fixture
def patch_subprocess_running_ping(monkeypatch): def patch_subprocess_running_ping(monkeypatch):
def inner(mock_obj): def inner(mock_obj):
@ -86,56 +99,102 @@ def set_os_windows(monkeypatch):
monkeypatch.setattr("sys.platform", "win32") monkeypatch.setattr("sys.platform", "win32")
@pytest.fixture
def mock_agent_event_queue():
return MagicMock(spec=IAgentEventQueue)
HOST_IP = "192.168.1.1"
TIMEOUT = 1.0
def _get_ping_scan_event(result: PingScanData):
return PingScanEvent(
source=get_agent_id(),
target=HOST_IP,
timestamp=TIMESTAMP,
tags=frozenset(),
response_received=result.response_received,
os=result.os,
)
@pytest.mark.usefixtures("set_os_linux") @pytest.mark.usefixtures("set_os_linux")
def test_linux_ping_success(patch_subprocess_running_ping_with_ping_output): def test_linux_ping_success(patch_subprocess_running_ping_with_ping_output, mock_agent_event_queue):
patch_subprocess_running_ping_with_ping_output(LINUX_SUCCESS_OUTPUT) patch_subprocess_running_ping_with_ping_output(LINUX_SUCCESS_OUTPUT)
result = ping("192.168.1.1", 1.0) result = ping(HOST_IP, TIMEOUT, mock_agent_event_queue)
event = _get_ping_scan_event(result)
assert result.response_received assert result.response_received
assert result.os == OperatingSystem.LINUX assert result.os == OperatingSystem.LINUX
assert mock_agent_event_queue.publish.call_count == 1
mock_agent_event_queue.publish.assert_called_with(event)
@pytest.mark.usefixtures("set_os_linux") @pytest.mark.usefixtures("set_os_linux")
def test_linux_ping_no_response(patch_subprocess_running_ping_with_ping_output): def test_linux_ping_no_response(
patch_subprocess_running_ping_with_ping_output, mock_agent_event_queue
):
patch_subprocess_running_ping_with_ping_output(LINUX_NO_RESPONSE_OUTPUT) patch_subprocess_running_ping_with_ping_output(LINUX_NO_RESPONSE_OUTPUT)
result = ping("192.168.1.1", 1.0) result = ping(HOST_IP, TIMEOUT, mock_agent_event_queue)
event = _get_ping_scan_event(result)
assert not result.response_received assert not result.response_received
assert result.os is None assert result.os is None
assert mock_agent_event_queue.publish.call_count == 1
mock_agent_event_queue.publish.assert_called_with(event)
@pytest.mark.usefixtures("set_os_windows") @pytest.mark.usefixtures("set_os_windows")
def test_windows_ping_success(patch_subprocess_running_ping_with_ping_output): def test_windows_ping_success(
patch_subprocess_running_ping_with_ping_output, mock_agent_event_queue
):
patch_subprocess_running_ping_with_ping_output(WINDOWS_SUCCESS_OUTPUT) patch_subprocess_running_ping_with_ping_output(WINDOWS_SUCCESS_OUTPUT)
result = ping("192.168.1.1", 1.0) result = ping(HOST_IP, TIMEOUT, mock_agent_event_queue)
event = _get_ping_scan_event(result)
assert result.response_received assert result.response_received
assert result.os == OperatingSystem.WINDOWS assert result.os == OperatingSystem.WINDOWS
assert mock_agent_event_queue.publish.call_count == 1
mock_agent_event_queue.publish.assert_called_with(event)
@pytest.mark.usefixtures("set_os_windows") @pytest.mark.usefixtures("set_os_windows")
def test_windows_ping_no_response(patch_subprocess_running_ping_with_ping_output): def test_windows_ping_no_response(
patch_subprocess_running_ping_with_ping_output, mock_agent_event_queue
):
patch_subprocess_running_ping_with_ping_output(WINDOWS_NO_RESPONSE_OUTPUT) patch_subprocess_running_ping_with_ping_output(WINDOWS_NO_RESPONSE_OUTPUT)
result = ping("192.168.1.1", 1.0) result = ping(HOST_IP, TIMEOUT, mock_agent_event_queue)
event = _get_ping_scan_event(result)
assert not result.response_received assert not result.response_received
assert result.os is None assert result.os is None
assert mock_agent_event_queue.publish.call_count == 1
mock_agent_event_queue.publish.assert_called_with(event)
def test_malformed_ping_command_response(patch_subprocess_running_ping_with_ping_output): def test_malformed_ping_command_response(
patch_subprocess_running_ping_with_ping_output, mock_agent_event_queue
):
patch_subprocess_running_ping_with_ping_output(MALFORMED_OUTPUT) patch_subprocess_running_ping_with_ping_output(MALFORMED_OUTPUT)
result = ping("192.168.1.1", 1.0) result = ping(HOST_IP, TIMEOUT, mock_agent_event_queue)
event = _get_ping_scan_event(result)
assert not result.response_received assert not result.response_received
assert result.os is None assert result.os is None
assert mock_agent_event_queue.publish.call_count == 1
mock_agent_event_queue.publish.assert_called_with(event)
@pytest.mark.usefixtures("patch_subprocess_running_ping_to_raise_timeout_expired") @pytest.mark.usefixtures("patch_subprocess_running_ping_to_raise_timeout_expired")
def test_timeout_expired(): def test_timeout_expired(mock_agent_event_queue):
result = ping("192.168.1.1", 1.0) result = ping(HOST_IP, TIMEOUT, mock_agent_event_queue)
event = _get_ping_scan_event(result)
assert not result.response_received assert not result.response_received
assert result.os is None assert result.os is None
assert mock_agent_event_queue.publish.call_count == 1
mock_agent_event_queue.publish.assert_called_with(event)
@pytest.fixture @pytest.fixture
@ -147,9 +206,9 @@ def ping_command_spy(monkeypatch):
@pytest.fixture @pytest.fixture
def assert_expected_timeout(ping_command_spy): def assert_expected_timeout(ping_command_spy, mock_agent_event_queue):
def inner(timeout_flag, timeout_input, expected_timeout): def inner(timeout_flag, timeout_input, expected_timeout):
ping("192.168.1.1", timeout_input) ping(HOST_IP, timeout_input, mock_agent_event_queue)
assert ping_command_spy.call_args is not None assert ping_command_spy.call_args is not None
@ -159,6 +218,8 @@ def assert_expected_timeout(ping_command_spy):
timeout_flag_index = ping_command.index(timeout_flag) timeout_flag_index = ping_command.index(timeout_flag)
assert ping_command[timeout_flag_index + 1] == expected_timeout assert ping_command[timeout_flag_index + 1] == expected_timeout
assert mock_agent_event_queue.publish.call_count == 1
return inner return inner
@ -178,8 +239,9 @@ def test_linux_timeout(assert_expected_timeout):
assert_expected_timeout(timeout_flag, timeout, str(math.ceil(timeout))) assert_expected_timeout(timeout_flag, timeout, str(math.ceil(timeout)))
def test_exception_handling(monkeypatch): def test_exception_handling(monkeypatch, mock_agent_event_queue):
monkeypatch.setattr( monkeypatch.setattr(
"infection_monkey.network_scanning.ping_scanner._ping", MagicMock(side_effect=Exception) "infection_monkey.network_scanning.ping_scanner._ping", MagicMock(side_effect=Exception)
) )
assert ping("abc", 10) == EMPTY_PING_SCAN assert ping("abc", 10, mock_agent_event_queue) == EMPTY_PING_SCAN
assert mock_agent_event_queue.publish.call_count == 0

View File

@ -1,13 +1,14 @@
import threading import threading
from unittest.mock import MagicMock from unittest.mock import MagicMock
from common.event_queue import IAgentEventQueue
from common.types import PingScanData from common.types import PingScanData
from infection_monkey.i_puppet import PluginType from infection_monkey.i_puppet import PluginType
from infection_monkey.puppet.puppet import EMPTY_FINGERPRINT, Puppet from infection_monkey.puppet.puppet import EMPTY_FINGERPRINT, Puppet
def test_puppet_run_payload_success(): def test_puppet_run_payload_success():
p = Puppet() p = Puppet(agent_event_queue=MagicMock(spec=IAgentEventQueue))
payload = MagicMock() payload = MagicMock()
payload_name = "PayloadOne" payload_name = "PayloadOne"
@ -19,7 +20,7 @@ def test_puppet_run_payload_success():
def test_puppet_run_multiple_payloads(): def test_puppet_run_multiple_payloads():
p = Puppet() p = Puppet(agent_event_queue=MagicMock(spec=IAgentEventQueue))
payload_1 = MagicMock() payload_1 = MagicMock()
payload1_name = "PayloadOne" payload1_name = "PayloadOne"
@ -45,6 +46,6 @@ def test_puppet_run_multiple_payloads():
def test_fingerprint_exception_handling(monkeypatch): def test_fingerprint_exception_handling(monkeypatch):
p = Puppet() p = Puppet(agent_event_queue=MagicMock(spec=IAgentEventQueue))
p._plugin_registry.get_plugin = MagicMock(side_effect=Exception) p._plugin_registry.get_plugin = MagicMock(side_effect=Exception)
assert p.fingerprint("", "", PingScanData("windows", False), {}, {}) == EMPTY_FINGERPRINT assert p.fingerprint("", "", PingScanData("windows", False), {}, {}) == EMPTY_FINGERPRINT