Esempio n. 1
0
    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\ADMIN$" % self.connection.server_name)
        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.open(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
        service_path = r'"%SystemRoot%\{0}" -service'.format(self._exe_file)
        log.info("Creating PAExec service %s" % self.service_name)
        self._service.create(service_path)
Esempio n. 2
0
 def _delete_file(self, tree, name):
     file_open = Open(tree, name)
     file_open.open(
         ImpersonationLevel.Impersonation, FilePipePrinterAccessMask.DELETE,
         FileAttributes.FILE_ATTRIBUTE_NORMAL, 0,
         CreateDisposition.FILE_OPEN_IF,
         CreateOptions.FILE_NON_DIRECTORY_FILE
         | CreateOptions.FILE_DELETE_ON_CLOSE)
     file_open.close(get_attributes=False)
Esempio n. 3
0
    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\ADMIN$" % self.connection.server_name)
        smb_tree.connect()

        share = Open(smb_tree, "")
        share.open(
            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)
        try:
            files = share.query_directory(
                "PAExec-*.exe", FileInformationClass.FILE_NAMES_INFORMATION)
        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)
Esempio n. 4
0
    def _get_paexec_files_and_services(self, client):
        server = os.environ['PYPSEXEC_SERVER']
        username = os.environ['PYPSEXEC_USERNAME']
        password = os.environ['PYPSEXEC_PASSWORD']
        paexec_services = []

        # need to close and reopen the connection to ensure deletes are
        # processed
        client.disconnect()
        client = Client(server, username=username, password=password)
        client.connect()
        scmr = client._service._scmr
        scmr_handle = client._service._scmr_handle

        services = scmr.enum_services_status_w(
            scmr_handle, ServiceType.SERVICE_WIN32_OWN_PROCESS,
            EnumServiceState.SERVICE_STATE_ALL)
        for service in services:
            if service['service_name'].lower().startswith("paexec"):
                paexec_services.append(service['service_name'])

        smb_tree = TreeConnect(client.session,
                               r"\\%s\ADMIN$" % client.connection.server_name)
        smb_tree.connect()

        share = Open(smb_tree, "")
        share.open(
            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)
        try:
            paexec_files = share.query_directory(
                "PAExec-*.exe", FileInformationClass.FILE_NAMES_INFORMATION)
        except SMBResponseException as exc:
            if exc.status != NtStatus.STATUS_NO_SUCH_FILE:
                raise exc
            paexec_files = []

        return client, paexec_services, paexec_files
Esempio n. 5
0
def open_pipe(tree, name, access_mask, fsctl_wait=False):
    """
    Opened the requested pipe with the access mask specified. Will attempt
    to connect 3 times before failing in case the pipe's don't exist at the
    time.

    :param tree: The SMB TreeConnect of IPC$ to connect to
    :param name: The name of the pipe to connect to
    :param access_mask: The access mask to apply to the Open
    :param fsctl_wait: Runs the FSCTL_PIPE_WAIT command over an
        SMB2IOCTLRequest
    :return: A connected Open() object of the pipe
    """
    log.info("Creating SMB Open for pipe: %s" % name)
    pipe = Open(tree, name)

    if fsctl_wait:
        wait_pipe = SMB2IOCTLRequest()
        wait_pipe['ctl_code'] = CtlCode.FSCTL_PIPE_WAIT
        wait_pipe['file_id'] = b"\xff" * 16
        wait_pipe['flags'] = IOCTLFlags.SMB2_0_IOCTL_IS_FSCTL

        fsctl_data = FSCTLPipeWait()
        fsctl_data['name'] = name.encode('utf-16-le')
        wait_pipe['buffer'] = fsctl_data

        log.info("Sending FSCTL_PIPE_WAIT for pipe %s" % name)
        log.debug(str(fsctl_data))
        request = tree.session.connection.send(wait_pipe,
                                               sid=tree.session.session_id,
                                               tid=tree.tree_connect_id)

        log.info("Receiving FSCTL_PIPE_WAIT response for pipe: %s" % name)
        tree.session.connection.receive(request)

    pipe.open(
        ImpersonationLevel.Impersonation, access_mask,
        FileAttributes.FILE_ATTRIBUTE_NORMAL, 0, CreateDisposition.FILE_OPEN,
        CreateOptions.FILE_NON_DIRECTORY_FILE
        | CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT)

    return pipe
Esempio n. 6
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 and username and password:
            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.open(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(binascii.unhexlify(PAEXEC_DATA), 0)
                paexec_file.close(get_attributes=False)

                yield session
            finally:
                paexec_file.open(ImpersonationLevel.Impersonation,
                                 FilePipePrinterAccessMask.DELETE,
                                 FileAttributes.FILE_ATTRIBUTE_NORMAL, 0,
                                 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")
Esempio n. 7
0
class SCMRApi(object):
    def __init__(self, smb_session):
        # connect to the IPC tree and open a handle at svcctl
        self.tree = TreeConnect(
            smb_session, r"\\%s\IPC$" % smb_session.connection.server_name)
        self.handle = Open(self.tree, "svcctl")
        self.call_id = 0

    def open(self):
        log.debug("Connecting to SMB Tree %s for SCMR" % self.tree.share_name)
        self.tree.connect()

        log.debug("Opening handle to svcctl pipe")
        self.handle.open(
            ImpersonationLevel.Impersonation,
            FilePipePrinterAccessMask.GENERIC_READ
            | FilePipePrinterAccessMask.GENERIC_WRITE, 0,
            ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE
            | ShareAccess.FILE_SHARE_DELETE, CreateDisposition.FILE_OPEN,
            CreateOptions.FILE_NON_DIRECTORY_FILE)

        # we need to bind svcctl to SCManagerW over DCE/RPC
        bind = BindPDU()
        bind['pfx_flags'].set_flag(PFlags.PFC_FIRST_FRAG)
        bind['pfx_flags'].set_flag(PFlags.PFC_LAST_FRAG)
        bind['packed_drep'] = DataRepresentationFormat()
        bind['call_id'] = self.call_id
        self.call_id += 1

        context_ndr = ContextElement()
        context_ndr['context_id'] = 0
        context_ndr['abstract_syntax'] = SyntaxIdElement()
        context_ndr['abstract_syntax']['uuid'] = \
            uuid.UUID("367ABB81-9844-35F1-AD32-98F038001003")
        context_ndr['abstract_syntax']['version'] = 2

        # https://msdn.microsoft.com/en-us/library/cc243843.aspx
        ndr_syntax = SyntaxIdElement()
        ndr_syntax['uuid'] = uuid.UUID("8a885d04-1ceb-11c9-9fe8-08002b104860")
        ndr_syntax['version'] = 2
        context_ndr['transfer_syntaxes'] = [ndr_syntax]

        context_bind = ContextElement()
        context_bind['context_id'] = 1
        context_bind['abstract_syntax'] = SyntaxIdElement()
        context_bind['abstract_syntax']['uuid'] = \
            uuid.UUID("367ABB81-9844-35F1-AD32-98F038001003")
        context_bind['abstract_syntax']['version'] = 2

        # https://msdn.microsoft.com/en-us/library/cc243715.aspx
        # uuid prefix = 6CB71C2C-9812-4540
        # uuid prefix bytes = b'\x2c\x1c\xb7\x6c\x12\x98\x40\x45'
        # BindTimeFeatureNegotiateBitmask
        # https://msdn.microsoft.com/en-us/library/cc243884.aspx
        # SecurityContextMultiplexingSupported = 0x01
        # KeepConnectionOnOrphanSupported = 0x02
        # version number is 1
        bind_syntax = SyntaxIdElement()
        bind_syntax['uuid'] = b'\x2c\x1c\xb7\x6c\x12\x98\x40\x45' \
                              b'\x03\x00\x00\x00\x00\x00\x00\x00'
        bind_syntax['version'] = 1
        context_bind['transfer_syntaxes'] = [bind_syntax]

        bind['context_elems'] = [context_ndr, context_bind]
        bind_data = bind.pack()

        log.info("Sending bind request to svcctl")
        log.debug(str(bind))
        self.handle.write(bind_data)

        log.info("Receiving bind result for svcctl")
        bind_data = self.handle.read(0, 1024)
        bind_result = parse_pdu(bind_data)
        log.debug(str(bind_result))
        if not isinstance(bind_result, BindAckPDU):
            raise PDUException("Expecting BindAckPDU for initial bind result "
                               "but got: %s" % str(bind_result))

    def close(self):
        log.info("Closing bind to svcctl")
        self.handle.close(False)
        self.tree.disconnect()

    # SCMR Functions below

    def close_service_handle_w(self, handle):
        # https://msdn.microsoft.com/en-us/library/cc245920.aspx
        opnum = 0

        res = self._invoke("RCloseServiceHandleW", opnum, handle)
        handle = res[:20]
        return_code = struct.unpack("<i", res[20:])[0]
        self._parse_error(return_code, "RCloseServiceHandleW")
        return handle

    def control_service(self, service_handle, control_code):
        # https://msdn.microsoft.com/en-us/library/cc245921.aspx
        opnum = 1

        data = service_handle
        data += struct.pack("<i", control_code)

        res = self._invoke("RControlService", opnum, data)
        return_code = struct.unpack("<i", res[-4:])[0]
        self._parse_error(return_code, "RControlService")

        service_status = ServiceStatus()
        service_status.unpack(res[:-4])

        return service_status

    def delete_service(self, service_handle):
        # https://msdn.microsoft.com/en-us/library/cc245926.aspx
        opnum = 2

        res = self._invoke("RDeleteService", opnum, service_handle)
        return_code = struct.unpack("<i", res)[0]
        self._parse_error(return_code, "RDeleteService")

    def query_service_status(self, service_handle):
        # https://msdn.microsoft.com/en-us/library/cc245952.aspx
        opnum = 6

        res = self._invoke("RQueryServiceStatus", opnum, service_handle)
        return_code = struct.unpack("<i", res[-4:])[0]
        self._parse_error(return_code, "RQueryServiceStatus")

        service_status = ServiceStatus()
        service_status.unpack(res[:-4])

        return service_status

    def enum_services_status_w(self, server_handle, service_type,
                               service_state):
        """
        Enumerates the services based on the criteria selected

        :param server_handle: A handle to SCMR
        :param service_type: ServiceType flags to filter by service type
        :param service_state: EnumServiceState enum value
        :return: List dictionaries with the following entries
            service_name: The service name of the service
            display_name: The display name of the service
            service_status: ServiceStatus structure of the service
        """
        # https://msdn.microsoft.com/en-us/library/cc245933.aspx
        opnum = 14

        # sent 0 bytes on the buffer size for the 1st request to get the
        # buffer size that is required
        req_data = server_handle
        req_data += struct.pack("<i", service_type)
        req_data += struct.pack("<i", service_state)
        req_data += struct.pack("<i", 0)
        req_data += b"\x00\x00\x00\x00"
        res = self._invoke("REnumServicesStatusW", opnum, req_data)

        # now send another request with the total buffer size sent
        buffer_size = struct.unpack("<i", res[4:8])[0]
        req_data = server_handle
        req_data += struct.pack("<i", service_type)
        req_data += struct.pack("<i", service_state)
        req_data += res[4:8]
        req_data += b"\x00\x00\x00\x00"

        try:
            res = self._invoke("REnumServicesStatusW", opnum, req_data)
            data = res
        except SMBResponseException as exc:
            if exc.status != NtStatus.STATUS_BUFFER_OVERFLOW:
                raise exc

            ioctl_resp = SMB2IOCTLResponse()
            ioctl_resp.unpack(exc.header['data'].get_value())
            pdu_resp = self._parse_pdu(ioctl_resp['buffer'].get_value(), opnum)
            read_data = self.handle.read(0, 3256)  # 4280 - 1024
            data = pdu_resp + read_data

        while len(data) < buffer_size:
            read_data = self.handle.read(0, 4280)
            data += self._parse_pdu(read_data, opnum)

        return_code = struct.unpack("<i", data[-4:])[0]
        self._parse_error(return_code, "REnumServicesStatusW")

        # now we have all the data, let's unpack it
        services = []
        services_returned = struct.unpack("<i", data[-12:-8])[0]
        offset = 4
        for i in range(0, services_returned):
            name_offset = struct.unpack("<i", data[offset:4 + offset])[0]
            disp_offset = struct.unpack("<i", data[4 + offset:8 + offset])[0]
            service_status = ServiceStatus()
            service_name = data[name_offset + 4:].split(b"\x00\x00")[0]
            display_name = data[disp_offset + 4:].split(b"\x00\x00")[0]
            service_status.unpack(data[offset + 8:])

            service_info = {
                "display_name": (display_name + b"\x00").decode('utf-16-le'),
                "service_name": (service_name + b"\x00").decode('utf-16-le'),
                "service_status": service_status
            }
            services.append(service_info)
            offset += 8 + len(service_status)

        return services

    def open_sc_manager_w(self, machine_name, database_name, desired_access):
        # https://msdn.microsoft.com/en-us/library/cc245942.aspx
        opnum = 15

        data = self._marshal_string(machine_name, unique=True)
        data += self._marshal_string(database_name)
        data += struct.pack("<i", desired_access)

        res = self._invoke("ROpenSCManagerW", opnum, data)
        server_handle = res[:20]
        return_code = struct.unpack("<i", res[20:])[0]
        self._parse_error(return_code, "ROpenSCManagerW")
        return server_handle

    def open_service_w(self, server_handle, service_name, desired_access):
        # https://msdn.microsoft.com/en-us/library/cc245944.aspx
        opnum = 16

        data = server_handle
        data += self._marshal_string(service_name)
        data += struct.pack("<i", desired_access)

        res = self._invoke("ROpenServiceW", opnum, data)
        service_handle = res[:20]
        return_code = struct.unpack("<i", res[20:])[0]
        self._parse_error(return_code, "ROpenServiceW")
        return service_handle

    def start_service_w(self, service_handle, *args):
        opnum = 19

        data = service_handle
        data += struct.pack("<i", len(args))
        data += b"".join([self._marshal_string(arg) for arg in args])
        data += b"\x00" * 4  # terminate arg list

        res = self._invoke("RStartServiceW", opnum, data)
        return_code = struct.unpack("<i", res)[0]
        self._parse_error(return_code, "RStartServiceW")

    def create_service_wow64_w(self, server_handle, service_name, display_name,
                               desired_access, service_type, start_type,
                               error_control, path, load_order_group, tag_id,
                               dependencies, username, password):
        # https://msdn.microsoft.com/en-us/library/cc245925.aspx
        opnum = 45

        data = server_handle
        data += self._marshal_string(service_name)
        data += self._marshal_string(display_name, unique=True)
        data += struct.pack("<i", desired_access)
        data += struct.pack("<i", service_type)
        data += struct.pack("<i", start_type)
        data += struct.pack("<i", error_control)
        data += self._marshal_string(path)
        data += self._marshal_string(load_order_group)
        data += struct.pack("<i", tag_id)

        # TODO: convert list of string to a byte object
        dependencies_bytes = dependencies if dependencies else b"\x00" * 6
        data += dependencies_bytes
        dependencies_length = len(dependencies) if dependencies else 0
        data += struct.pack("<i", dependencies_length)

        data += self._marshal_string(username)

        pass_bytes = self._marshal_string(password)
        data += pass_bytes
        pass_len = len(pass_bytes) if password else 0
        data += struct.pack("<i", pass_len)

        res = self._invoke("RCreateServiceWOW64W", opnum, data)
        tag_id = res[0:4]
        service_handle = res[4:24]
        return_code = struct.unpack("<i", res[24:])[0]
        self._parse_error(return_code, "RCreateServiceWOW64W")
        return tag_id, service_handle

    def _invoke(self, function_name, opnum, data):
        req = RequestPDU()
        req['pfx_flags'].set_flag(PFlags.PFC_FIRST_FRAG)
        req['pfx_flags'].set_flag(PFlags.PFC_LAST_FRAG)
        req['packed_drep'] = DataRepresentationFormat()
        req['call_id'] = self.call_id
        self.call_id += 1

        req['opnum'] = opnum
        req['stub_data'] = data

        ioctl_request = SMB2IOCTLRequest()
        ioctl_request['ctl_code'] = CtlCode.FSCTL_PIPE_TRANSCEIVE
        ioctl_request['file_id'] = self.handle.file_id
        ioctl_request['max_output_response'] = 1024
        ioctl_request['flags'] = IOCTLFlags.SMB2_0_IOCTL_IS_FSCTL
        ioctl_request['buffer'] = req

        session_id = self.tree.session.session_id
        tree_id = self.tree.tree_connect_id
        log.info("Sending svcctl RPC request for %s" % function_name)
        log.debug(str(req))
        request = self.tree.session.connection.send(ioctl_request,
                                                    sid=session_id,
                                                    tid=tree_id)
        log.info("Receiving svcctl RPC response for %s" % function_name)
        resp = self.tree.session.connection.receive(request)
        ioctl_resp = SMB2IOCTLResponse()
        ioctl_resp.unpack(resp['data'].get_value())
        log.debug(str(ioctl_resp))

        pdu_resp = self._parse_pdu(ioctl_resp['buffer'].get_value(), opnum)
        return pdu_resp

    def _parse_pdu(self, data, opnum):
        pdu_resp = parse_pdu(data)
        if not isinstance(pdu_resp, ResponsePDU):
            raise PDUException("Expecting ResponsePDU for opnum %d response "
                               "but got: %s" % (opnum, str(pdu_resp)))
        return pdu_resp['stub_data'].get_value()

    def _parse_error(self, return_code, function_name):
        error_string = "ERROR_UNKNOWN"
        for error_name, error_val in vars(ScmrReturnValues).items():
            if isinstance(error_val, int) and error_val == return_code:
                error_string = error_name
                break
        if not error_string.startswith("ERROR_SUCCESS"):
            raise SCMRException(function_name, return_code, error_string)

    def _marshal_string(self, string, unique=False, max_count=None):
        """
        Strings are encoding as a UTF-16-LE byte structure and are marshalled
        in a particular format to send over RPC. The format is as follows

            Referent ID (Int32): A unique ID for the string, we just set to 1
            Max Count (Int32): If the server can return a value, this is the
                size that can be returned in the buffer otherwise just the
                numbers of chars in the input string
            Offset (Int32): The offset of the string, defaults to 0
            Actual Count (Int32): The number of chars (not bytes) or the string
                itself including the NULL terminator
            Bytes (Bytes): The string encoded as a UTF-16-LE byte string with
                a NULL terminator
            Padding (Bytes): The value must align to a 4-byte boundary so this
                is some NULL bytes to pad the length

        :param string: The string to marshal
        :param unique: Whether the string is unique and requires an ID
        :return: A byte string of the marshaled string
        """
        # return NULL Pointer for a null string
        if not string:
            return b"\x00" * 4

        referent = b"\x00\x00\x00\x01" if unique else b""
        unicode_string = string.encode("utf-16-le") + b"\x00\x00"
        unicode_count = int(len(unicode_string) / 2)
        count = struct.pack("<i", unicode_count)
        offset = b"\x00" * 4
        bytes = referent + count + offset + count + unicode_string

        # each parameter needs to be aligned at a 4-byte boundary so get the
        # padding length if necessary
        mod = len(bytes) % 4
        padding_len = 0 if mod == 0 else 4 - mod
        return bytes + (b"\x00" * padding_len)