def test_invalid_user(self, smb_real):
     connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
     connection.connect()
     try:
         session = Session(connection, "fakeuser", "fakepass")
         with pytest.raises(SMBAuthenticationError) as exc:
             session.connect()
         assert "Failed to authenticate with server: " in str(exc.value)
     finally:
         connection.disconnect(True)
Beispiel #2
0
 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)
Beispiel #3
0
 def test_require_encryption_not_supported(self, smb_real):
     connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
     connection.connect(Dialects.SMB_2_1_0)
     try:
         session = Session(connection, smb_real[0], smb_real[1])
         with pytest.raises(SMBException) as exc:
             session.connect()
         assert str(exc.value) == "SMB encryption is required but the " \
                                  "connection does not support it"
     finally:
         connection.disconnect(True)
Beispiel #4
0
 def test_dialect_2_encrypted_share(self, smb_real):
     connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
     connection.connect(Dialects.SMB_2_1_0)
     session = Session(connection, smb_real[0], smb_real[1], False)
     tree = TreeConnect(session, smb_real[5])
     try:
         session.connect()
         with pytest.raises(AccessDenied) as exc:
             tree.connect()
     finally:
         connection.disconnect(True)
Beispiel #5
0
 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()
Beispiel #6
0
 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()
Beispiel #7
0
 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()
Beispiel #8
0
 def test_dialect_2_encrypted_share(self, smb_real):
     connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
     connection.connect(Dialects.SMB_2_1_0)
     session = Session(connection, smb_real[0], smb_real[1], False)
     tree = TreeConnect(session, smb_real[5])
     try:
         session.connect()
         with pytest.raises(SMBResponseException) as exc:
             tree.connect()
         assert str(exc.value) == "Received unexpected status from the " \
                                  "server: (3221225506) " \
                                  "STATUS_ACCESS_DENIED: 0xc0000022"
     finally:
         connection.disconnect(True)
Beispiel #9
0
 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()
Beispiel #10
0
 def test_secure_negotiation_verification_failed(self, smb_real):
     connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
     connection.connect(Dialects.SMB_3_0_2)
     session = Session(connection, smb_real[0], smb_real[1])
     connection.dialect = Dialects.SMB_3_0_0
     tree = TreeConnect(session, smb_real[4])
     try:
         session.connect()
         with pytest.raises(SMBException) as exc:
             tree.connect()
         assert "Secure negotiate failed to verify server dialect, " \
                "Actual: 770, Expected: 768" in str(exc.value)
     finally:
         connection.disconnect(True)
Beispiel #11
0
 def test_dialect_3_encrypted_share(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])
     tree = TreeConnect(session, smb_real[5])
     try:
         session.connect()
         tree.connect()
         assert tree.encrypt_data
         assert not tree.is_ca_share
         assert not tree.is_dfs_share
         assert not tree.is_scaleout_share
         assert isinstance(tree.tree_connect_id, int)
     finally:
         connection.disconnect(True)
Beispiel #12
0
    def test_connect_fail(self, smb_real, monkeypatch, mocker):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        try:
            monkeypatch.setattr(
                pyspnego, 'client',
                mocker.MagicMock(
                    side_effect=pyspnego.exceptions.NoCredentialError()))
            session = Session(connection, smb_real[0], smb_real[1])

            with pytest.raises(SMBAuthenticationError,
                               match="Failed to authenticate with server"):
                session.connect()

        finally:
            connection.disconnect(True)
Beispiel #13
0
 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)
Beispiel #14
0
 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)
Beispiel #15
0
 def test_secure_ignore_negotiation_verification_failed(self, smb_real):
     connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
     connection.connect(Dialects.SMB_3_0_2)
     session = Session(connection, smb_real[0], smb_real[1])
     connection.dialect = Dialects.SMB_3_0_0
     tree = TreeConnect(session, smb_real[4])
     try:
         session.connect()
         tree.connect(False)
         assert not tree.encrypt_data
         assert not tree.is_ca_share
         assert not tree.is_dfs_share
         assert not tree.is_scaleout_share
         assert isinstance(tree.tree_connect_id, int)
     finally:
         connection.disconnect(True)
         tree.disconnect()  # test that disconnect can be run mutliple times
Beispiel #16
0
    def test_broken_message_worker_closed_socket(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3], True)
        connection.connect()
        try:
            test_msg = SMB2NegotiateRequest()
            test_req = Request(test_msg, type(test_msg), connection)
            connection.outstanding_requests[666] = test_req

            # Close the connection manually
            connection.transport.close()

            with pytest.raises(SMBConnectionClosed):
                connection.receive(test_req)

            with pytest.raises(SMBConnectionClosed):
                connection.send(SMB2NegotiateRequest())
        finally:
            connection.disconnect()
Beispiel #17
0
 def test_verify_mistmatch(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()
         header = connection.preauth_integrity_hash_value[-2]
         # just set some random values for verifiation failure
         header['flags'].set_flag(Smb2Flags.SMB2_FLAGS_SIGNED)
         header['signature'] = b"\xff" * 16
         with pytest.raises(SMBException) as exc:
             connection._verify(header,
                                header['session_id'].get_value(),
                                verify_session=True)
         assert "Server message signature could not be verified:" in \
             str(exc.value)
     finally:
         connection.disconnect(True)
    def test_change_notify_cancel(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection,
                          smb_real[0],
                          smb_real[1],
                          require_encryption=False)
        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

            # Makes sure that we cancel after the async response has been returned from the server.
            while watcher._request.async_id is None:
                pass

            assert watcher.result is None

            watcher.cancel()

            watcher.wait()
            assert watcher.cancelled is True
            assert watcher.result is None

            # Make sure it doesn't cause any weird errors when calling it again
            watcher.cancel()
        finally:
            connection.disconnect(True)
Beispiel #19
0
 def test_dialect_2_1_0(self, smb_real):
     connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
     connection.connect(Dialects.SMB_2_1_0)
     session = Session(connection, smb_real[0], smb_real[1],
                       require_encryption=False)
     try:
         session.connect()
         assert len(session.application_key) == 16
         assert session.decryption_key is None
         assert not session.encrypt_data
         assert session.encryption_key is None
         assert len(session.preauth_integrity_hash_value) == 5
         assert not session.require_encryption
         assert session.session_id is not None
         assert session.session_key == session.application_key
         assert session.signing_key == session.signing_key
         assert session.signing_required
     finally:
         connection.disconnect(True)
Beispiel #20
0
    def session(self):
        server = os.environ.get('PYPSEXEC_SERVER', None)
        username = os.environ.get('PYPSEXEC_USERNAME', None)
        password = os.environ.get('PYPSEXEC_PASSWORD', None)

        if server:
            connection = Connection(uuid.uuid4(), server, 445)
            session = Session(connection, username, password)
            tree = TreeConnect(session, r"\\%s\ADMIN$" % server)
            paexec_file = Open(tree, "PAExec.exe")

            connection.connect()
            try:

                session.connect()
                tree.connect()

                paexec_file.create(ImpersonationLevel.Impersonation,
                                   FilePipePrinterAccessMask.FILE_WRITE_DATA,
                                   FileAttributes.FILE_ATTRIBUTE_NORMAL,
                                   ShareAccess.FILE_SHARE_READ,
                                   CreateDisposition.FILE_OVERWRITE_IF,
                                   CreateOptions.FILE_NON_DIRECTORY_FILE)
                paexec_file.write(pkgutil.get_data('pypsexec', 'paexec.exe'),
                                  0)
                paexec_file.close(get_attributes=False)

                yield session
            finally:
                paexec_file.create(ImpersonationLevel.Impersonation,
                                   FilePipePrinterAccessMask.DELETE,
                                   FileAttributes.FILE_ATTRIBUTE_NORMAL,
                                   ShareAccess.FILE_SHARE_DELETE,
                                   CreateDisposition.FILE_OVERWRITE_IF,
                                   CreateOptions.FILE_DELETE_ON_CLOSE)
                paexec_file.close(get_attributes=False)
                connection.disconnect(True)

        else:
            pytest.skip("PYPSEXEC_SERVER, PYPSEXEC_USERNAME, PYPSEXEC_PASSWORD"
                        " environment variables were not set. Integration "
                        "tests will be skipped")
Beispiel #21
0
    def test_broken_message_worker(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3], True)
        connection.connect()
        try:
            test_msg = SMB2NegotiateRequest()
            test_req = Request(test_msg, connection)
            connection.outstanding_requests[666] = test_req

            # Put a bad message in the incoming queue to break the worker in a bad way
            connection.transport._recv_queue.put(b"\x01\x02\x03\x04")
            while connection._t_exc is None:
                pass

            with pytest.raises(Exception):
                connection.send(SMB2NegotiateRequest())

            # Verify that all outstanding request events have been set on a failure
            assert test_req.response_event.is_set()
        finally:
            connection.disconnect()
Beispiel #22
0
    def test_dialect_2_1_0(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_2_1_0)
        try:
            assert connection.dialect == Dialects.SMB_2_1_0
            assert connection.negotiated_dialects == [Dialects.SMB_2_1_0]
            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 is None
            assert not connection.supports_encryption
            assert connection.require_signing
        finally:
            connection.disconnect()
 def test_verify_mistmatch(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()
         b_header = connection.preauth_integrity_hash_value[-2]
         header = SMB2HeaderResponse()
         header.unpack(b_header)
         # just set some random values for verification failure
         header['flags'].set_flag(Smb2Flags.SMB2_FLAGS_SIGNED)
         header['signature'] = b"\xff" * 16
         with pytest.raises(SMBException) as exc:
             connection.verify_signature(
                 header,
                 list(connection.session_table.keys())[0],
                 force=True)
         assert "Server message signature could not be verified:" in str(
             exc.value)
     finally:
         connection.disconnect(True)
Beispiel #24
0
 def test_setup_session_with_ms_gss_token(self, smb_real):
     connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
     connection.connect()
     connection.gss_negotiate_token = b"\x60\x76\x06\x06\x2b\x06\x01\x05" \
                                      b"\x05\x02\xa0\x6c\x30\x6a\xa0\x3c" \
                                      b"\x30\x3a\x06\x0a\x2b\x06\x01\x04" \
                                      b"\x01\x82\x37\x02\x02\x1e\x06\x09" \
                                      b"\x2a\x86\x48\x82\xf7\x12\x01\x02" \
                                      b"\x02\x06\x09\x2a\x86\x48\x86\xf7" \
                                      b"\x12\x01\x02\x02\x06\x0a\x2a\x86" \
                                      b"\x48\x86\xf7\x12\x01\x02\x02\x03" \
                                      b"\x06\x0a\x2b\x06\x01\x04\x01\x82" \
                                      b"\x37\x02\x02\x0a\xa3\x2a\x30\x28" \
                                      b"\xa0\x26\x1b\x24\x6e\x6f\x74\x5f" \
                                      b"\x64\x65\x66\x69\x6e\x65\x64\x5f" \
                                      b"\x69\x6e\x5f\x52\x46\x43\x34\x31" \
                                      b"\x37\x38\x40\x70\x6c\x65\x61\x73" \
                                      b"\x65\x5f\x69\x67\x6e\x6f\x72\x65"
     session = Session(connection, smb_real[0], smb_real[1], False)
     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(True)
Beispiel #25
0
    def test_dialect_3_1_1_not_require_signing(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3], False)
        connection.connect(Dialects.SMB_3_1_1)
        try:
            assert connection.dialect == Dialects.SMB_3_1_1
            assert connection.negotiated_dialects == [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_ENABLED

            # server settings override the require signing
            assert connection.server_security_mode & \
                SecurityMode.SMB2_NEGOTIATE_SIGNING_REQUIRED == \
                SecurityMode.SMB2_NEGOTIATE_SIGNING_REQUIRED
            assert connection.supports_encryption
            # for tests we set that server requires signing so that overrides
            # the client setting
            assert connection.require_signing
        finally:
            connection.disconnect()
Beispiel #26
0
 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.preauth_integrity_hash_value) == 5
         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()
Beispiel #27
0
        responses.append(response)

    dir_files = []
    for dir_file in responses[1]:
        dir_files.append(dir_file['file_name'].get_value().decode('utf-16-le'))

    print("Directory '%s\\%s' contains the files: '%s'" %
          (share, dir_name, "', '".join(dir_files)))

    # delete a directory (note the dir needs to be empty to delete on close)
    dir_open = Open(tree, dir_name)
    delete_msgs = [
        dir_open.create(ImpersonationLevel.Impersonation,
                        DirectoryAccessMask.DELETE,
                        FileAttributes.FILE_ATTRIBUTE_DIRECTORY,
                        0,
                        CreateDisposition.FILE_OPEN,
                        CreateOptions.FILE_DIRECTORY_FILE
                        | CreateOptions.FILE_DELETE_ON_CLOSE,
                        send=False),
        dir_open.close(False, send=False)
    ]
    delete_reqs = connection.send_compound([x[0] for x in delete_msgs],
                                           sid=session.session_id,
                                           tid=tree.tree_connect_id,
                                           related=True)
    for i, request in enumerate(delete_reqs):
        response = delete_msgs[i][1](request)
finally:
    connection.disconnect(True)
class Client(object):
    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

    @staticmethod  #added-----------------------------------------------------------------------
    def obscure_service_name():
        selection = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0987654321_- "
        stringbuilder = []

        for iter in range(0, random.randint(4, 16)):
            stringbuilder.append(random.choice(selection))

        return "".join(stringbuilder)

    def connect(self, timeout=60):
        log.info("Setting up SMB Connection to %s:%d" %
                 (self.server, self.port))
        self.connection.connect(timeout=timeout)
        log.info("Authenticating SMB Session")
        self.session.connect()
        log.info("Opening handle to SCMR and PAExec service")
        self._service.open()

    def disconnect(self):
        log.info("Closing handle of PAExec service and SCMR")
        self._service.close()
        log.info("Closing SMB Connection")
        self.connection.disconnect(close=False)

    def create_service(self):
        # check if the service exists and delete it
        log.debug("Ensuring service is deleted before starting")
        self._service.delete()

        # copy across the PAExec payload to C:\Windows\
        smb_tree = TreeConnect(
            self.session,
            r"\\%s\%s" % (self.connection.server_name, self._share))
        log.info("Connecting to SMB Tree %s" % smb_tree.share_name)
        smb_tree.connect()
        paexec_file = Open(smb_tree, self._exe_file)
        log.debug("Creating open to PAExec file")
        paexec_file.create(ImpersonationLevel.Impersonation,
                           FilePipePrinterAccessMask.FILE_WRITE_DATA,
                           FileAttributes.FILE_ATTRIBUTE_NORMAL,
                           ShareAccess.FILE_SHARE_READ,
                           CreateDisposition.FILE_OVERWRITE_IF,
                           CreateOptions.FILE_NON_DIRECTORY_FILE)
        log.info("Creating PAExec executable at %s\\%s" %
                 (smb_tree.share_name, self._exe_file))
        for (data, o) in paexec_out_stream(self.connection.max_write_size):
            paexec_file.write(data, o)
        log.debug("Closing open to PAExec file")
        paexec_file.close(False)
        log.info("Disconnecting from SMB Tree %s" % smb_tree.share_name)
        smb_tree.disconnect()

        # create the PAExec service
        # added ---------------------------------------------------------------
        path_root = r"%SystemRoot%"
        path_root = dir_map.get(self._share.strip("$\n\r\t"), r"%SystemRoot%")
        # added ---------------------------------------------------------------

        service_path = r'"{}\{}" -service'.format(path_root,
                                                  self._exe_file)  # added
        log.info("Creating PAExec service %s" % self.service_name)
        self._service.create(service_path)

    def remove_service(self):
        """
        Removes the PAExec service and executable that was created as part of
        the create_service function. This does not remove any older executables
        or services from previous runs, use cleanup() instead for that purpose.
        """
        # Stops/remove the PAExec service and removes the executable
        log.debug("Deleting PAExec service at the end of the process")
        self._service.delete()

        # delete the PAExec executable
        smb_tree = TreeConnect(
            self.session,
            r"\\%s\%s" % (self.connection.server_name, self._share))
        log.info("Connecting to SMB Tree %s" % smb_tree.share_name)
        smb_tree.connect()
        log.info("Creating open to PAExec file with delete on close flags")
        self._delete_file(smb_tree, self._exe_file)
        log.info("Disconnecting from SMB Tree %s" % smb_tree.share_name)
        smb_tree.disconnect()

    def cleanup(self):
        """
        Cleans up any old services or payloads that may have been left behind
        on a previous failure. This will search C:\Windows for any files
        starting with PAExec-*.exe and delete them. It will also stop and
        remove any services that start with PAExec-* if they exist.

        Before calling this function, the connect() function must have already
        been called.
        """
        scmr = self._service._scmr
        services = scmr.enum_services_status_w(
            self._service._scmr_handle, ServiceType.SERVICE_WIN32_OWN_PROCESS,
            EnumServiceState.SERVICE_STATE_ALL)
        for service in services:
            if service['service_name'].lower().startswith("paexec"):
                svc = Service(service['service_name'], self.session)
                svc.open()
                svc.delete()

        smb_tree = TreeConnect(
            self.session,
            r"\\%s\%s" % (self.connection.server_name, self._share))
        smb_tree.connect()

        share = Open(smb_tree, "")
        query_msgs = [
            share.create(ImpersonationLevel.Impersonation,
                         DirectoryAccessMask.FILE_READ_ATTRIBUTES
                         | DirectoryAccessMask.SYNCHRONIZE
                         | DirectoryAccessMask.FILE_LIST_DIRECTORY,
                         FileAttributes.FILE_ATTRIBUTE_DIRECTORY,
                         ShareAccess.FILE_SHARE_READ
                         | ShareAccess.FILE_SHARE_WRITE
                         | ShareAccess.FILE_SHARE_DELETE,
                         CreateDisposition.FILE_OPEN,
                         CreateOptions.FILE_DIRECTORY_FILE,
                         send=False),
            share.query_directory("PAExec-*.exe",
                                  FileInformationClass.FILE_NAMES_INFORMATION,
                                  send=False),
            share.close(False, send=False)
        ]
        query_reqs = self.connection.send_compound([x[0] for x in query_msgs],
                                                   self.session.session_id,
                                                   smb_tree.tree_connect_id,
                                                   related=True)
        # receive response for open and close
        query_msgs[0][1](query_reqs[0])
        query_msgs[2][1](query_reqs[2])
        try:
            # receive the response for query_directory
            files = query_msgs[1][1](query_reqs[1])
        except SMBResponseException as exc:
            if exc.status != NtStatus.STATUS_NO_SUCH_FILE:
                raise exc
            files = []

        for file in files:
            file_name = file['file_name'].get_value().decode('utf-16-le')
            self._delete_file(smb_tree, file_name)

    def run_executable(self,
                       executable,
                       arguments=None,
                       processors=None,
                       asynchronous=False,
                       load_profile=True,
                       interactive_session=0,
                       interactive=False,
                       run_elevated=False,
                       run_limited=False,
                       username=None,
                       password=None,
                       use_system_account=False,
                       working_dir=None,
                       show_ui_on_win_logon=False,
                       priority=ProcessPriority.NORMAL_PRIORITY_CLASS,
                       remote_log_path=None,
                       timeout_seconds=0,
                       stdout=OutputPipeBytes,
                       stderr=OutputPipeBytes,
                       stdin=None):
        """
        Runs a command over the PAExec/PSExec interface based on the options
        provided. At a minimum the executable argument is required and the
        rest can stay as the defaults.

        The default configuration for a process (with no changes) is;
            User: The user that authenticated the SMB Session
            Elevation: Highest possible
            Working Dir: %SYSTEM_ROOT%\System32
            Interactive: False
            Priority: Normal

        :param executable: (String) The executable to be run
        :param arguments: (String) Arguments to run with the executable
        :param processors: (List<Int>) The processors that the process can run
            on, default is all the processors
        :param asynchronous: (Bool) Whether to run the process and not wait for
            the output, it will continue to run in the background. The stdout
            and stderr return value will be None and the rc is not reflective
            of the running process
        :param load_profile: (Bool) Whether to load the user profile, default
            is True
        :param interactive_session: (Int) The session id that an
            interactive process will run on, use with interactive=True to
            run a process on an existing session
        :param interactive: (Bool) Whether to run on an interative session or
            not, default is False. The stdout and stderr will be None
        :param run_elevated: (Bool) Whether to run as an elevated process or
            not, default is False (This only applies when username is supplied)
        :param run_limited: (Bool) Whether to run as a limited user context,
            admin rights are removed, or not, default is False (This only
            applied when username is applied)
        :param username: (String) The username to run the process as, if not
            set then either the SMB Session account is used or
            NT AUTHORITY\SYSTEM (use_system_account=True) is used
        :param password: (String) The password for the username account
        :param use_system_account: (Bool) Whether to use the
            NT AUTHORITY\SYSTEM account isn't of a normal user
        :param working_dir: (String) The working directory that is used when
            spawning the process
        :param show_ui_on_win_logon: (Bool) Whether to display the UI on the
            Winlogon secure desktop (use_system_account=True only), default is
            False
        :param priority: (paexec.ProcessPriority) The process priority level,
            default is NORMAL_PRIORITY_CLASS
        :param remote_log_path: (String) A path on the remote host to output
            log files for the PAExec service process (for debugging purposes)
        :param timeout_seconds: (Int) A timeout that will force the PAExec
            process to stop once reached, default is 0 (no timeout)
        :param stdout: (pipe.OutputPipe) An class that implements of
            pipe.OutputPipe that handles the Named Pipe stdout output. The
            default is pipe.OutputPipeBytes which returns a byte string of the
            stdout
        :param stderr: (pipe.OutputPipe) An class that implements of
            pipe.OutputPipe that handles the Named Pipe stderr output. The
            default is pipe.OutputPipeBytes which returns a byte string of the
            stderr
        :param stdin: Either a byte string of generator that yields multiple
            byte strings to send over the stdin pipe.
        :return: Tuple(stdout, stderr, rc)
            stdout: (Bytes) The stdout.get_bytes() return result
            stderr: (Bytes) The stderr.get_bytes() return result
            rc: (Int) The return code of the process (The pid of the async
                process when async=True)
        """
        if run_elevated and run_limited:
            raise PypsexecException("Both run_elevated and run_limited are "
                                    "set, only 1 of these can be true")
        if stdin is not None and (asynchronous or interactive):
            raise PypsexecException("Cannot send stdin data on an interactive "
                                    "or asynchronous process")

        log.debug("Making sure PAExec service is running")
        self._service.start()

        smb_tree = TreeConnect(self.session,
                               r"\\%s\IPC$" % self.connection.server_name)
        log.info("Connecting to SMB Tree %s" % smb_tree.share_name)
        smb_tree.connect()

        settings = PAExecSettingsBuffer()
        settings['processors'] = processors if processors else []
        settings['asynchronous'] = asynchronous
        settings['dont_load_profile'] = not load_profile
        settings['interactive_session'] = interactive_session
        settings['interactive'] = interactive
        settings['run_elevated'] = run_elevated
        settings['run_limited'] = run_limited
        settings['username'] = self._encode_string(username)
        settings['password'] = self._encode_string(password)
        settings['use_system_account'] = use_system_account
        settings['working_dir'] = self._encode_string(working_dir)
        settings['show_ui_on_win_logon'] = show_ui_on_win_logon
        settings['priority'] = priority
        settings['executable'] = self._encode_string(executable)
        settings['arguments'] = self._encode_string(arguments)
        settings['remote_log_path'] = self._encode_string(remote_log_path)
        settings['timeout_seconds'] = timeout_seconds

        input_data = PAExecSettingsMsg()
        input_data['unique_id'] = self._unique_id
        input_data['buffer'] = settings

        # write the settings to the main PAExec pipe
        pipe_access_mask = FilePipePrinterAccessMask.GENERIC_READ | \
            FilePipePrinterAccessMask.GENERIC_WRITE | \
            FilePipePrinterAccessMask.FILE_APPEND_DATA | \
            FilePipePrinterAccessMask.READ_CONTROL | \
            FilePipePrinterAccessMask.SYNCHRONIZE
        for i in range(0, 3):
            try:
                main_pipe = open_pipe(smb_tree, self._exe_file,
                                      pipe_access_mask)
            except SMBResponseException as exc:
                if exc.status != NtStatus.STATUS_OBJECT_NAME_NOT_FOUND:
                    raise exc
                elif i == 2:
                    raise PypsexecException("Failed to open main PAExec pipe "
                                            "%s, no more attempts remaining" %
                                            self._exe_file)
                log.warning("Main pipe %s does not exist yet on attempt %d. "
                            "Trying again in 5 seconds" %
                            (self._exe_file, i + 1))
                time.sleep(5)
            else:
                break

        log.info("Writing PAExecSettingsMsg to the main PAExec pipe")
        log.info(str(input_data))
        main_pipe.write(input_data.pack(), 0)

        log.info("Reading PAExecMsg from the PAExec pipe")
        settings_resp_raw = main_pipe.read(0, 1024)
        settings_resp = PAExecMsg()
        settings_resp.unpack(settings_resp_raw)
        log.debug(str(settings_resp))
        settings_resp.check_resp()

        # start the process now
        start_msg = PAExecMsg()
        start_msg['msg_id'] = PAExecMsgId.MSGID_START_APP
        start_msg['unique_id'] = self._unique_id
        start_msg['buffer'] = PAExecStartBuffer()
        start_buffer = PAExecStartBuffer()
        start_buffer['process_id'] = self.pid
        start_buffer['comp_name'] = self.current_host.encode('utf-16-le')
        start_msg['buffer'] = start_buffer

        log.info("Writing PAExecMsg with PAExecStartBuffer to start the "
                 "remote process")
        log.debug(str(start_msg))
        main_pipe.write(start_msg.pack(), 0)

        if not interactive and not asynchronous:
            # create a pipe for stdout, stderr, and stdin and run in a separate
            # thread
            log.info("Connecting to remote pipes to retrieve output")
            stdout_pipe = stdout(smb_tree, self._stdout_pipe_name)
            stdout_pipe.start()
            stderr_pipe = stderr(smb_tree, self._stderr_pipe_name)
            stderr_pipe.start()
            stdin_pipe = InputPipe(smb_tree, self._stdin_pipe_name)

            # wait until the stdout and stderr pipes have sent their first
            # response
            log.debug("Waiting for stdout pipe to send first request")
            while not stdout_pipe.sent_first:
                pass
            log.debug("Waiting for stderr pipe to send first request")
            while not stderr_pipe.sent_first:
                pass

            # send any input if there was any
            try:
                if stdin and isinstance(stdin, bytes):
                    log.info("Sending stdin bytes over stdin pipe: %s" %
                             self._stdin_pipe_name)
                    stdin_pipe.write(stdin)
                elif stdin:
                    log.info("Sending stdin generator bytes over stdin pipe: "
                             "%s" % self._stdin_pipe_name)
                    for stdin_data in stdin():
                        stdin_pipe.write(stdin_data)
            except SMBResponseException as exc:
                # if it fails with a STATUS_PIPE_BROKEN exception, continue as
                # the actual error will be in the response (process failed)
                if exc.status != NtStatus.STATUS_PIPE_BROKEN:
                    raise exc
                log.warning("Failed to send data through stdin: %s" % str(exc))

        # read the final response from the process
        log.info("Reading result of PAExec process")
        exe_result_raw = main_pipe.read(0, 1024)
        log.info("Results read of PAExec process")

        if not interactive and not asynchronous:
            log.info("Closing PAExec std* pipes")
            stdout_pipe.close()
            stderr_pipe.close()
            stdin_pipe.close()
            log.info("Gettings stdout and stderr from pipe buffer queue")
            stdout_out = stdout_pipe.get_output()
            stderr_bytes = stderr_pipe.get_output()
        else:
            stdout_out = None
            stderr_bytes = None

        log.info("Closing main PAExec pipe")
        main_pipe.close()
        log.info("Disconnecting from SMB Tree %s" % smb_tree.share_name)
        smb_tree.disconnect()

        log.info("Unpacking PAExecMsg data from process result")
        exe_result = PAExecMsg()
        exe_result.unpack(exe_result_raw)
        log.debug(str(exe_result))
        exe_result.check_resp()
        log.debug("Unpacking PAExecReturnBuffer from main PAExecMsg")
        rc = PAExecReturnBuffer()
        rc.unpack(exe_result['buffer'].get_value())
        log.debug(str(rc))

        return_code = rc['return_code'].get_value()
        log.info("Process finished with exit code: %d" % return_code)
        log.debug("RC: %d" % return_code)
        return stdout_out, stderr_bytes, return_code

    def _encode_string(self, string):
        return string.encode('utf-16-le') if string else None

    def _empty_queue(self, queue):
        data = b""
        while True:
            try:
                data += queue.get(block=False)
            except Empty:
                break

        return data

    def _delete_file(self, tree, name):
        file_open = Open(tree, name)
        msgs = [
            file_open.create(ImpersonationLevel.Impersonation,
                             FilePipePrinterAccessMask.DELETE,
                             FileAttributes.FILE_ATTRIBUTE_NORMAL,
                             0,
                             CreateDisposition.FILE_OPEN_IF,
                             CreateOptions.FILE_NON_DIRECTORY_FILE
                             | CreateOptions.FILE_DELETE_ON_CLOSE,
                             send=False),
            file_open.close(get_attributes=False, send=False)
        ]
        reqs = self.connection.send_compound([x[0] for x in msgs],
                                             sid=self.session.session_id,
                                             tid=tree.tree_connect_id,
                                             related=True)
        # remove the responses from the SMB outstanding requests
        msgs[0][1](reqs[0])
        msgs[1][1](reqs[1])
    def test_change_notify_on_dir_compound(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()

        # Cannot use encryption as Samba has a bug where the transform response has the wrong Session Id. Also there's
        # a special edge case of testing the Session Id of the plaintext response with signatures so don't use
        # encryption.
        # https://bugzilla.samba.org/show_bug.cgi?id=14189
        session = Session(connection,
                          smb_real[0],
                          smb_real[1],
                          require_encryption=False)
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "directory-watch")
        try:
            session.connect()
            tree.connect()

            # Ensure the dir is clean of files.
            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)
            self._remove_file(tree, "directory-watch\\created file")
            open.close()

            watcher = FileSystemWatcher(open)
            messages = [
                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,
                            send=False),
                watcher.start(CompletionFilter.FILE_NOTIFY_CHANGE_FILE_NAME,
                              send=False)
            ]

            assert watcher.result is None
            assert watcher.response_event.is_set() is False

            requests = connection.send_compound([m[0] for m in messages],
                                                sid=session.session_id,
                                                tid=tree.tree_connect_id,
                                                related=True)
            [messages[i][1](req) for i, req in enumerate(requests)]

            # Run the wait in a separate thread so we can create the dir
            def watcher_wait():
                watcher.wait()

            watcher_wait_thread = threading.Thread(target=watcher_wait)
            watcher_wait_thread.daemon = True
            watcher_wait_thread.start()

            def watcher_event():
                watcher.response_event.wait()

            watcher_event_thread = threading.Thread(target=watcher_event)
            watcher_event_thread.daemon = True
            watcher_event_thread.start()

            # Create the new file
            file_open = Open(tree, "directory-watch\\created file")
            file_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)
            file_open.close()

            watcher_wait_thread.join(timeout=2)
            watcher_event_thread.join(timeout=2)
            assert watcher_wait_thread.is_alive() is False
            assert watcher_event_thread.is_alive() is False

            assert watcher.response_event.is_set()
            assert len(watcher.result) == 1

            assert watcher.result[0]['file_name'].get_value(
            ) == u"created file"
            assert watcher.result[0]['action'].get_value(
            ) == FileAction.FILE_ACTION_ADDED

            open.close()
        finally:
            connection.disconnect(True)
    def test_change_notify_no_data(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)

            self._remove_file(tree, "directory-watch\\created file")

            watcher = FileSystemWatcher(open)
            watcher.start(CompletionFilter.FILE_NOTIFY_CHANGE_FILE_NAME,
                          output_buffer_length=0)
            assert watcher.result is None
            assert watcher.response_event.is_set() is False

            # Run the wait in a separate thread so we can create the dir
            def watcher_wait():
                watcher.wait()

            watcher_wait_thread = threading.Thread(target=watcher_wait)
            watcher_wait_thread.daemon = True
            watcher_wait_thread.start()

            def watcher_event():
                watcher.response_event.wait()

            watcher_event_thread = threading.Thread(target=watcher_event)
            watcher_event_thread.daemon = True
            watcher_event_thread.start()

            # Create the new file
            file_open = Open(tree, "directory-watch\\created file")
            file_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)
            file_open.close()

            watcher_wait_thread.join(timeout=2)
            watcher_event_thread.join(timeout=2)
            assert watcher_wait_thread.is_alive() is False
            assert watcher_event_thread.is_alive() is False

            assert watcher.response_event.is_set()
            assert watcher.result == []

            open.close()
        finally:
            connection.disconnect(True)