Skip to content

Command Helpers

These are helpers related to executing shell commands. They are used throughout BBOT and its modules for executing various binaries such as nmap, nuclei, etc.

These helpers can be invoked directly from self.helpers, but inside a module they should always use self.run_process() or self.run_process_live(). These are light wrappers which ensure the running process is tracked by the module so that it can be easily terminated should the user need to kill the module:

# simple subprocess
ls_result = await self.run_process("ls", "-l")
for line ls_result.stdout.splitlines():
    # ...

# iterate through each line in real time
async for line in self.run_process_live(["grep", "-R"]):
    # ...

run async

run(self, *command, check=False, text=True, **kwargs)

Runs a command asynchronously and gets its output as a string.

This method is a simple helper for executing a command and capturing its output.
If an error occurs during execution, it can optionally raise an error or just log the stderr.

Args:
    *command (str): The command to run as separate arguments.
    check (bool, optional): If set to True, raises an error if the subprocess exits with a non-zero status.
                            Defaults to False.
    text (bool, optional): If set to True, decodes the subprocess output to string. Defaults to True.
    **kwargs (dict): Additional keyword arguments for the subprocess.

Returns:
    CompletedProcess: A completed process object with attributes for the command, return code, stdout, and stderr.

Raises:
    CalledProcessError: If the subprocess exits with a non-zero status and `check=True`.

Examples:
    >>> process = await run(["ls", "/tmp"])
    >>> process.stdout
    "file1.txt

file2.txt"

Source code in bbot/core/helpers/command.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
async def run(self, *command, check=False, text=True, **kwargs):
    """Runs a command asynchronously and gets its output as a string.

    This method is a simple helper for executing a command and capturing its output.
    If an error occurs during execution, it can optionally raise an error or just log the stderr.

    Args:
        *command (str): The command to run as separate arguments.
        check (bool, optional): If set to True, raises an error if the subprocess exits with a non-zero status.
                                Defaults to False.
        text (bool, optional): If set to True, decodes the subprocess output to string. Defaults to True.
        **kwargs (dict): Additional keyword arguments for the subprocess.

    Returns:
        CompletedProcess: A completed process object with attributes for the command, return code, stdout, and stderr.

    Raises:
        CalledProcessError: If the subprocess exits with a non-zero status and `check=True`.

    Examples:
        >>> process = await run(["ls", "/tmp"])
        >>> process.stdout
        "file1.txt\nfile2.txt"
    """
    # proc_tracker optionally keeps track of which processes are running under which modules
    # this allows for graceful SIGINTing of a module's processes in the case when it's killed
    proc_tracker = kwargs.pop("_proc_tracker", set())
    proc, _input, command = await self._spawn_proc(*command, **kwargs)
    if proc is not None:
        proc_tracker.add(proc)
        try:
            if _input is not None:
                if isinstance(_input, (list, tuple)):
                    _input = b"\n".join(smart_encode(i) for i in _input) + b"\n"
                else:
                    _input = smart_encode(_input)
            stdout, stderr = await proc.communicate(_input)

            # surface stderr
            if text:
                if stderr is not None:
                    stderr = smart_decode(stderr)
                if stdout is not None:
                    stdout = smart_decode(stdout)
            if proc.returncode:
                if check:
                    raise CalledProcessError(proc.returncode, command, output=stdout, stderr=stderr)
                if stderr:
                    command_str = " ".join(command)
                    log.warning(f"Stderr for run({command_str}):\n\t{stderr}")

            return CompletedProcess(command, proc.returncode, stdout, stderr)
        finally:
            proc_tracker.remove(proc)

run_live async

run_live(self, *command, check=False, text=True, **kwargs)

Runs a command asynchronously and iterates through its output line by line in realtime.

This method is useful for executing a command and capturing its output on-the-fly, as it is generated. If an error occurs during execution, it can optionally raise an error or just log the stderr.

Parameters:

  • *command (str, default: () ) –

    The command to run as separate arguments.

  • check (bool, default: False ) –

    If set to True, raises an error if the subprocess exits with a non-zero status. Defaults to False.

  • text (bool, default: True ) –

    If set to True, decodes the subprocess output to string. Defaults to True.

  • **kwargs (dict, default: {} ) –

    Additional keyword arguments for the subprocess.

Yields:

  • str or bytes: The output lines of the command, either as a decoded string (if text=True) or as bytes (if text=False).

Raises:

Examples:

>>> async for line in run_live(["tail", "-f", "/var/log/auth.log"]):
...     log.info(line)
Source code in bbot/core/helpers/command.py
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
async def run_live(self, *command, check=False, text=True, **kwargs):
    """Runs a command asynchronously and iterates through its output line by line in realtime.

    This method is useful for executing a command and capturing its output on-the-fly, as it is generated.
    If an error occurs during execution, it can optionally raise an error or just log the stderr.

    Args:
        *command (str): The command to run as separate arguments.
        check (bool, optional): If set to True, raises an error if the subprocess exits with a non-zero status.
                                Defaults to False.
        text (bool, optional): If set to True, decodes the subprocess output to string. Defaults to True.
        **kwargs (dict): Additional keyword arguments for the subprocess.

    Yields:
        str or bytes: The output lines of the command, either as a decoded string (if `text=True`)
                      or as bytes (if `text=False`).

    Raises:
        CalledProcessError: If the subprocess exits with a non-zero status and `check=True`.

    Examples:
        >>> async for line in run_live(["tail", "-f", "/var/log/auth.log"]):
        ...     log.info(line)
    """
    # proc_tracker optionally keeps track of which processes are running under which modules
    # this allows for graceful SIGINTing of a module's processes in the case when it's killed
    proc_tracker = kwargs.pop("_proc_tracker", set())
    proc, _input, command = await self._spawn_proc(*command, **kwargs)
    if proc is not None:
        proc_tracker.add(proc)
        try:
            input_task = None
            if _input is not None:
                input_task = asyncio.create_task(_write_stdin(proc, _input))

            while 1:
                try:
                    line = await proc.stdout.readline()
                except ValueError as e:
                    command_str = " ".join([str(c) for c in command])
                    log.warning(f"Error executing command {command_str}: {e}")
                    log.trace(traceback.format_exc())
                    continue
                if not line:
                    break
                if text:
                    line = smart_decode(line).rstrip("\r\n")
                else:
                    line = line.rstrip(b"\r\n")
                yield line

            if input_task is not None:
                try:
                    await input_task
                except ConnectionError:
                    log.trace(f"ConnectionError in command: {command}, kwargs={kwargs}")
                    log.trace(traceback.format_exc())
            await proc.wait()

            if proc.returncode:
                stdout, stderr = await proc.communicate()
                if text:
                    if stderr is not None:
                        stderr = smart_decode(stderr)
                    if stdout is not None:
                        stdout = smart_decode(stdout)
                if check:
                    raise CalledProcessError(proc.returncode, command, output=stdout, stderr=stderr)
                # surface stderr
                if stderr:
                    command_str = " ".join(command)
                    log.warning(f"Stderr for run_live({command_str}):\n\t{stderr}")
        finally:
            proc_tracker.remove(proc)