def __init__(self, server, username=None, password=None, port=445, encrypt=True): self.server = server self.port = port self.pid = os.getpid() self.current_host = socket.gethostname() self.connection = Connection(uuid.uuid4(), server, port) self.session = Session(self.connection, username, password, require_encryption=encrypt) self.service_name = "PAExec-%d-%s" % (self.pid, self.current_host) log.info("Creating PyPsexec Client with unique name: %s" % self.service_name) self._exe_file = "%s.exe" % self.service_name self._stdout_pipe_name = "PaExecOut%s%d"\ % (self.current_host, self.pid) self._stderr_pipe_name = "PaExecErr%s%d"\ % (self.current_host, self.pid) self._stdin_pipe_name = "PaExecIn%s%d" % (self.current_host, self.pid) self._unique_id = get_unique_id(self.pid, self.current_host) log.info("Generated unique ID for PyPsexec Client: %d" % self._unique_id) self._service = Service(self.service_name, self.session)
def test_encrypt_ccm(self, monkeypatch): def mockurandom(length): return b"\xff" * length monkeypatch.setattr(os, 'urandom', mockurandom) connection = Connection(uuid.uuid4(), "server", 445) connection.dialect = Dialects.SMB_3_1_1 connection.cipher_id = Ciphers.get_cipher(Ciphers.AES_128_CCM) session = Session(connection, "user", "pass") session.session_id = 1 session.encryption_key = b"\xff" * 16 expected = SMB2TransformHeader() expected['signature'] = b"\xc8\x73\x0c\x9b\xa7\xe5\x9f\x1c" \ b"\xfd\x37\x51\xa1\x95\xf2\xb3\xac" expected['nonce'] = b"\xff" * 11 + b"\x00" * 5 expected['original_message_size'] = 4 expected['flags'] = 1 expected['session_id'] = 1 expected['data'] = b"\x21\x91\xe3\x0e" actual = connection._encrypt(b"\x01\x02\x03\x04", session) assert isinstance(actual, SMB2TransformHeader) assert actual.pack() == expected.pack()
def test_encrypt_gcm(self, monkeypatch): def mockurandom(length): return b"\xff" * length monkeypatch.setattr(os, 'urandom', mockurandom) connection = Connection(uuid.uuid4(), "server", 445) connection.dialect = Dialects.SMB_3_1_1 connection.cipher_id = Ciphers.get_cipher(Ciphers.AES_128_GCM) session = Session(connection, "user", "pass") session.session_id = 1 session.encryption_key = b"\xff" * 16 expected = SMB2TransformHeader() expected['signature'] = b"\x39\xd8\x32\x34\xd7\x53\xd0\x8e" \ b"\xc0\xfc\xbe\x33\x01\x5f\x19\xbd" expected['nonce'] = b"\xff" * 12 + b"\x00" * 4 expected['original_message_size'] = 4 expected['flags'] = 1 expected['session_id'] = 1 expected['data'] = b"\xda\x26\x57\x33" actual = connection._encrypt(b"\x01\x02\x03\x04", session) assert isinstance(actual, SMB2TransformHeader) assert actual.pack() == expected.pack()
def register_session(server, username=None, password=None, port=445, encrypt=None, connection_timeout=60, connection_cache=None, auth_protocol='negotiate'): """ Creates an active connection and session to the server specified. This can be manually called to register the credentials of a specific server instead of defining it on the first function connecting to the server. The opened connection is registered in a pool and re-used if a connection is made to the same server with the same credentials. :param server: The server name to register. :param username: Optional username to connect with. Required if no session has been registered for the server and Kerberos auth is not being used. :param password: Optional password to connect with. :param port: The port to connect with. :param encrypt: Whether to force encryption or not, once this has been set to True the session cannot be changed back to False. :param connection_timeout: Override the timeout used for the initial connection. :param connection_cache: Connection cache to be used with :param auth_protocol: The protocol to use for authentication. Possible values are 'negotiate', 'ntlm' or 'kerberos'. Defaults to 'negotiate'. :return: The Session that was registered or already existed in the pool. """ connection_key = "%s:%s" % (server.lower(), port) if connection_cache is None: connection_cache = _SMB_CONNECTIONS connection = connection_cache.get(connection_key, None) # Make sure we ignore any connections that may have had a closed connection if not connection or not connection.transport.connected: connection = Connection(ClientConfig().client_guid, server, port) connection.connect(timeout=connection_timeout) connection_cache[connection_key] = connection # Find the first session in the connection session list that match the username specified, if not username then # just use the first session found or fall back to creating a new one with implicit auth/kerberos. session = next((s for s in connection.session_table.values() if username is None or s.username == username), None) if not session: session = Session(connection, username=username, password=password, require_encryption=(encrypt is True), auth_protocol=auth_protocol) session.connect() elif encrypt is not None: # We cannot go from encryption to no encryption on an existing session but we can do the opposite. if session.encrypt_data and not encrypt: raise ValueError( "Cannot disable encryption on an already negotiated session.") elif not session.encrypt_data and encrypt: session.encrypt = True return session
def test_connection(server, port): conn = Connection(uuid.uuid4(), server, port=port) print("Opening connection to %s:%d" % (server, port)) conn.connect(timeout=5) try: print("Connection successful, sending ECHO request") conn.echo() finally: conn.disconnect(True)
def test_verify_message_skip(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3], True) connection.connect() try: header = SMB2HeaderResponse() header['message_id'] = 0xFFFFFFFFFFFFFFFF connection.verify_signature(header, 0) finally: connection.disconnect()
def test_broken_message_worker_exception(self, mocker): connection = Connection(uuid.uuid4(), 'server', 445, True) mock_transport = mocker.MagicMock() connection.transport = mock_transport connection.transport.recv.side_effect = Exception('test') connection._process_message_thread() with pytest.raises(Exception, match='test'): connection.send(SMB2Echo()) with pytest.raises(Exception, match='test'): connection.receive(None)
def test_parse_error_unknown(self): connection = Connection(uuid.uuid4(), "server", 445) session = Session(connection, "user", "password") api = SCMRApi(session) with pytest.raises(SCMRException) as exc: api._parse_error(999, "function_name") assert str(exc.value) == "Exception calling function_name. Code: 999" \ ", Msg: ERROR_UNKNOWN"
def test_marshal_string_none(self): connection = Connection(uuid.uuid4(), "server", 445) session = Session(connection, "user", "password") api = SCMRApi(session) expected = b"\x00\x00\x00\x00" actual = api._marshal_string(None) assert actual == expected
def bule_screen(IP, username=None, password=None, port=445, encode=None, connectionTimeout=10): _SMB_CONNECTIONS = {} connection_key = "%s:%s" %(IP, port) connection = _SMB_CONNECTIONS.get(connection_key, None) if not connection: connection = Connection(uuid.uuid4(), IP, port) connection.connect(timeout=connectionTimeout) _SMB_CONNECTIONS[connection_key] = connection session = next((s for s in connection.session_table.values() if username is None or s.username == username), None) if not session: session = Session(connection, username=username, password=password, require_encryption=(encode is True)) session.connect() elif encode is not None: if session.encrypt_data and not encode: print("[\033[33m-\033[0m]Cannot disable encryption on an already negotiated session.") elif not session.encrypt_data and encode: session.encrypt = True return session
def test_requested_credits_greater_than_available(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3], True) connection.connect() try: msg = SMB2IOCTLRequest() msg['max_output_response'] = 65538 # results in 2 credits required with pytest.raises(SMBException) as exc: connection._generate_packet_header(msg, None, None, 0) assert str(exc.value) == "Request requires 2 credits but only 1 " \ "credits are available" finally: connection.disconnect()
def test_parse_pdu_failure(self): connection = Connection(uuid.uuid4(), "server", 445) session = Session(connection, "user", "password") api = SCMRApi(session) fault_pdu = FaultPDU() fault_pdu['packed_drep'] = DataRepresentationFormat() with pytest.raises(PDUException) as exc: api._parse_pdu(fault_pdu.pack(), 10) assert "Expecting ResponsePDU for opnum 10 response but got: " \ "FaultPDU" in str(exc.value)
def test_verify_message_skip(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3], True) connection.connect() try: header = SMB2HeaderResponse() header['message_id'] = 0xFFFFFFFFFFFFFFFF expected = header.pack() connection._verify(header, 0) actual = header.pack() assert actual == expected finally: connection.disconnect()
def test_parse_pdu_fine(self): connection = Connection(uuid.uuid4(), "server", 445) session = Session(connection, "user", "password") api = SCMRApi(session) response_pdu = ResponsePDU() response_pdu['packed_drep'] = DataRepresentationFormat() response_pdu['stub_data'] = b"\x01\x02\x03\x04" expected = b"\x01\x02\x03\x04" actual = api._parse_pdu(response_pdu.pack(), 10) assert actual == expected
def test_verify_fail_no_session(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3], True) connection.connect() try: header = SMB2HeaderResponse() header['message_id'] = 1 header['flags'].set_flag(Smb2Flags.SMB2_FLAGS_SIGNED) with pytest.raises(SMBException) as exc: connection._verify(header, 100) assert str(exc.value) == "Failed to find session 100 for " \ "message verification" finally: connection.disconnect()
def test_decrypt_invalid_flag(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3], True) session = Session(connection, smb_real[0], smb_real[1]) connection.connect() try: session.connect() # just get some random message header = connection.preauth_integrity_hash_value[-1] enc_header = connection._encrypt(header.pack(), session) assert isinstance(enc_header, SMB2TransformHeader) enc_header['flags'] = 5 with pytest.raises(SMBException) as exc: connection._decrypt(enc_header) assert str(exc.value) == "Expecting flag of 0x0001 but got 5 in " \ "the SMB Transform Header Response" finally: connection.disconnect(True)
def test_decrypt_invalid_session_id(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3], True) session = Session(connection, smb_real[0], smb_real[1]) connection.connect() try: session.connect() # just get some random message header = connection.preauth_integrity_hash_value[-1] enc_header = connection._encrypt(header.pack(), session) assert isinstance(enc_header, SMB2TransformHeader) enc_header['session_id'] = 100 with pytest.raises(SMBException) as exc: connection._decrypt(enc_header) assert str(exc.value) == "Failed to find valid session 100 for " \ "message decryption" finally: connection.disconnect(True)
def register_session(server, username=None, password=None, port=445, encrypt=None, connection_timeout=60): """ Creates an active connection and session to the server specified. This can be manually called to register the credentials of a specific server instead of defining it on the first function connecting to the server. The opened connection is registered in a pool and re-used if a connection is made to the same server with the same credentials. :param server: The server name to register. :param username: Optional username to connect with. Required if no session has been registered for the server and Kerberos auth is not being used. :param password: Optional password to connect with. :param port: The port to connect with. :param encrypt: Whether to force encryption or not, once this has been set to True the session cannot be changed back to False. :param connection_timeout: Override the timeout used for the initial connection. :return: The Session that was registered or already existed in the pool. """ connection_key = "%s:%s" % (server, port) global _SMB_CONNECTIONS connection = _SMB_CONNECTIONS.get(connection_key, None) if not connection: connection = Connection(_CLIENT_GUID, server, port) connection.connect(timeout=connection_timeout) _SMB_CONNECTIONS[connection_key] = connection # Find the first session in the connection session list that match the username specified, if not username then # just use the first session found or fall back to creating a new one with implicit auth/kerberos. session = next((s for s in connection.session_table.values() if username is None or s.username == username), None) if not session: session = Session(connection, username=username, password=password, require_encryption=(encrypt is True)) session.connect() elif encrypt is not None: # We cannot go from encryption to no encryption on an existing session but we can do the opposite. if session.encrypt_data and not encrypt: raise ValueError("Cannot disable encryption on an already negotiated session.") elif not session.encrypt_data and encrypt: session.encrypt = True return session
def __init__(self, server, username, password, port=445): connection_id = uuid.uuid4() addr = socket.gethostbyname(server) self.server = server connection = Connection(connection_id, addr, port, require_signing=True) self.session = Session(connection, username, password, require_encryption=False)
def test_connection_echo(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect() session = Session(connection, smb_real[0], smb_real[1]) session.connect() try: actual = connection.echo(sid=session.session_id, credit_request=2) assert actual == 2 finally: connection.disconnect(True)
def test_dialect_implicit_require_signing(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3], True) connection.connect() try: assert connection.dialect == Dialects.SMB_3_1_1 assert connection.negotiated_dialects == [ Dialects.SMB_2_0_2, Dialects.SMB_2_1_0, Dialects.SMB_3_0_0, Dialects.SMB_3_0_2, Dialects.SMB_3_1_1 ] assert connection.gss_negotiate_token is not None assert len(connection.preauth_integrity_hash_value) == 2 assert len(connection.salt) == 32 assert connection.sequence_window['low'] == 1 assert connection.sequence_window['high'] == 2 assert connection.client_security_mode == \ SecurityMode.SMB2_NEGOTIATE_SIGNING_REQUIRED # server settings override the require signing assert connection.server_security_mode == \ SecurityMode.SMB2_NEGOTIATE_SIGNING_REQUIRED | \ SecurityMode.SMB2_NEGOTIATE_SIGNING_ENABLED assert connection.supports_encryption assert connection.require_signing finally: connection.disconnect()
def test_change_notify_on_a_file(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect() session = Session(connection, smb_real[0], smb_real[1]) tree = TreeConnect(session, smb_real[4]) open = Open(tree, "file-watch.txt") try: session.connect() tree.connect() open.create( ImpersonationLevel.Impersonation, FilePipePrinterAccessMask.MAXIMUM_ALLOWED, FileAttributes.FILE_ATTRIBUTE_NORMAL, ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE | ShareAccess.FILE_SHARE_DELETE, CreateDisposition.FILE_OPEN_IF, CreateOptions.FILE_NON_DIRECTORY_FILE) watcher = FileSystemWatcher(open) watcher.start(CompletionFilter.FILE_NOTIFY_CHANGE_FILE_NAME) expected = "Received unexpected status from the server: (3221225485) STATUS_INVALID_PARAMETER" with pytest.raises(SMBResponseException, match=re.escape(expected)): watcher.wait() finally: connection.disconnect(True)
def test_send_invalid_tree_id(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) session = Session(connection, smb_real[0], smb_real[1]) connection.connect() try: session.connect() msg = SMB2IOCTLRequest() msg['file_id'] = b"\xff" * 16 with pytest.raises(SMBException) as exc: connection.send(msg, session.session_id, 10) assert str(exc.value) == "Cannot find Tree with the ID 10 in " \ "the session tree table" finally: connection.disconnect()
def __init__(self, server, username=None, password=None, port=445, encrypt=True, obscure=True, sharename="ADMIN$"): self.server = server self.port = port self.pid = os.getpid() self.current_host = socket.gethostname() self.connection = Connection(uuid.uuid4(), server, port) self.session = Session(self.connection, username, password, require_encryption=encrypt) self.service_name = "" #added if obscure: #added self.service_name = Client.obscure_service_name() #added else: #added self.service_name = "PAExec" # This doesn't need a new name everytime the service is created; makes a mess in the directories # OLD = "PAExec-%d-%s" % (self.pid, self.current_host) log.info("Creating PyPsexec Client with unique name: %s" % self.service_name) self._exe_file = "%s.exe" % self.service_name self._stdout_pipe_name = "PaExecOut%s%d" % (self.current_host, self.pid) self._stderr_pipe_name = "PaExecErr%s%d" % (self.current_host, self.pid) self._stdin_pipe_name = "PaExecIn%s%d" % (self.current_host, self.pid) self._unique_id = get_unique_id(self.pid, self.current_host) log.info("Generated unique ID for PyPsexec Client: %d" % self._unique_id) self._service = Service(self.service_name, self.session) self._share = sharename
def test_dialect_3_1_1(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_1_1) session = Session(connection, smb_real[0], smb_real[1]) try: session.connect() assert len(session.application_key) == 16 assert session.application_key != session.session_key assert len(session.decryption_key) == 16 assert session.decryption_key != session.session_key assert session.encrypt_data assert len(session.encryption_key) == 16 assert session.encryption_key != session.session_key assert len(session.connection.preauth_integrity_hash_value) == 2 assert len(session.preauth_integrity_hash_value) == 3 assert session.require_encryption assert session.session_id is not None assert len(session.session_key) == 16 assert len(session.signing_key) == 16 assert session.signing_key != session.session_key assert not session.signing_required finally: connection.disconnect(True) # test that disconnect can be run multiple times session.disconnect()
def test_change_notify_underlying_close(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect() session = Session(connection, smb_real[0], smb_real[1]) tree = TreeConnect(session, smb_real[4]) open = Open(tree, "directory-watch") try: session.connect() tree.connect() open.create( ImpersonationLevel.Impersonation, DirectoryAccessMask.MAXIMUM_ALLOWED, FileAttributes.FILE_ATTRIBUTE_DIRECTORY, ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE | ShareAccess.FILE_SHARE_DELETE, CreateDisposition.FILE_OPEN_IF, CreateOptions.FILE_DIRECTORY_FILE) watcher = FileSystemWatcher(open) watcher.start(CompletionFilter.FILE_NOTIFY_CHANGE_FILE_NAME) assert watcher.result is None assert watcher.response_event.is_set() is False open.close() expected = "Received unexpected status from the server: (267) STATUS_NOTIFY_CLEANUP" with pytest.raises(SMBResponseException, match=re.escape(expected)): watcher.wait() finally: connection.disconnect(True)
def test_setup_session_with_ntlm_only(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect() session = Session(connection, smb_real[0], smb_real[1], False, auth_protocol='ntlm') try: session.connect() assert len(session.application_key) == 16 assert session.application_key != session.session_key assert len(session.decryption_key) == 16 assert session.decryption_key != session.session_key assert not session.encrypt_data assert len(session.encryption_key) == 16 assert session.encryption_key != session.session_key assert len(session.connection.preauth_integrity_hash_value) == 2 assert len(session.preauth_integrity_hash_value) == 3 assert not session.require_encryption assert session.session_id is not None assert len(session.session_key) == 16 assert len(session.signing_key) == 16 assert session.signing_key != session.session_key assert session.signing_required finally: connection.disconnect()
def test_change_notify_on_a_file(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect() session = Session(connection, smb_real[0], smb_real[1]) tree = TreeConnect(session, smb_real[4]) open = Open(tree, "file-watch.txt") try: session.connect() tree.connect() open.create( ImpersonationLevel.Impersonation, FilePipePrinterAccessMask.MAXIMUM_ALLOWED, FileAttributes.FILE_ATTRIBUTE_NORMAL, ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE | ShareAccess.FILE_SHARE_DELETE, CreateDisposition.FILE_OPEN_IF, CreateOptions.FILE_NON_DIRECTORY_FILE) watcher = FileSystemWatcher(open) watcher.start(CompletionFilter.FILE_NOTIFY_CHANGE_FILE_NAME) with pytest.raises(InvalidParameter): watcher.wait() finally: connection.disconnect(True)
def check(self, ip_addr, port=445): """Checks whether a SMB server allows login without credentials""" try: # connect and attempt authentication connection = Connection(uuid.uuid4(), ip_addr, port, require_signing=False) connection.connect(Dialects.SMB_2_0_2, timeout=TIMEOUT) try: session = Session( connection, "", "", require_encryption=False ) # dont require encryption or signing to support o2 HomeBox try: session.connect() except SMBAuthenticationError: return False return ("", "") finally: connection.disconnect() except Exception as e: print("SMB connection failed") print(e) return False
def __init__(self, server, username, password, port=445): connection_id = uuid.uuid4() addr = socket.getaddrinfo(server, None, 0, 0, socket.IPPROTO_TCP)[0][4][0] self.server = server connection = Connection(connection_id, addr, port, require_signing=True) self.session = Session(connection, username, password, require_encryption=False)