Exemplo n.º 1
0
def sc(process_event, ctx):
    """ :type process_event: Process """

    if process_event.state.exe.lower() == "sc.exe" and "binpath=" in str(
            process_event.state.command_line.lower()):
        args = command_to_argv(process_event.state.command_line)

        criteria = Criteria(
            parent_image_path="C:\\windows\\system32\\services.exe")
        if len(args) > 2 and args[1].startswith("\\\\"):
            host_string = args[1]
            host_info = extract_host(host_string)
            if 'hostname' in host_info:
                criteria['hostname'] = host_info['hostname']

        else:
            # if the system is not directly defined, then the host stays the same
            criteria['hostname'] = process_event.state.hostname

        # Get the case version of the command
        bin_path = next_arg(args, "binPath=")
        if bin_path is None:
            return

        logger.debug(
            "Pivoting onto SC command with command line {}".format(bin_path))
        criteria['command_line'] = bin_path.replace("'", '"')

        for process_event in ctx.query(ProcessEvent.search(
                'create', **criteria),
                                       start=process_event.time):
            task_process = ProcessEvent.update_existing(
                'create', **process_event)
            yield task_process
Exemplo n.º 2
0
def registry_key_run(registry_event, ctx=None):
    """ Given a process creation, find all registry modifications.
    :type process_event: ProcessEvent
    """
    if not registry_event.state.data:
        return

    data = registry_event.state.data
    window = {'start': registry_event.time - datetime.timedelta(minutes=2)}

    query = ProcessEvent.search(action='create', image_path=data) | ProcessEvent.search(action='create', command_line=data)
    for result in ctx.query(query, **window):
        yield ProcessEvent.update_existing(action='create', **result)
Exemplo n.º 3
0
def process_end(process_event, ctx=None):
    """ Given a process creation, lookup the termination to gather the end time.
    The difference on the two may be used to calculate the duration.

    This pivot should be performed first, which will ensure that other pivots have a better window,
    knowing both start and end time. Thus it must not be performed asynchronously

    :type process_event: ProcessEvent
    """
    terminate_window = {'start': process_event.time - datetime.timedelta(seconds=1)}

    # Get the time where the pid is reused (if it is)
    criteria = Criteria(pid=process_event.state.pid, hostname=process_event.state['hostname'])

    reuse_query = ctx.query(ProcessEvent.search(action='create', **criteria),
                            first=1, start=process_event.time + datetime.timedelta(minutes=5))

    for result in reuse_query:
        reused_pid = ProcessEvent.update_existing(action='create', **result)

        # Round the time down more
        terminate_window['end'] = max(reused_pid.time - datetime.timedelta(minutes=10),
                                      process_event.time + datetime.timedelta(seconds=1))

    # Now shrink the window, knowing when the pid is reused. Look for the correct terminate event
    terminate_query = ctx.query(ProcessEvent.search(action='terminate', **criteria), first=1, **terminate_window)

    for result in terminate_query:
        # Update with the original fields from the 'create' event
        state = process_event.state.to_mongo().to_dict()
        state.update(result['state'])
        result['state'] = state

        process_terminate = ProcessEvent.update_existing(action='terminate', **result)

        # Calculate the duration and update the parent
        duration = process_terminate.time - process_event.time
        # for sanity's sake, avoid non-zero duration (if logs came in at the same time)
        duration = datetime.timedelta(seconds=1) if duration.total_seconds() == 0 else duration
        process_event.duration = duration

        yield process_terminate

    if process_event.duration is None:
        if terminate_window.get('end', None) is not None:
            logger.warning('[PIVOTS] No end time detected, using time of pid reuse')
            duration = terminate_window['end'] - process_event.time
            # process will auto-update state.duration
            process_event.duration = duration
Exemplo n.º 4
0
def get_process_context(event, ctx=None):
    """ Identifies the process event that corresponds to the pid and hostname of another generic DataModelEvent

    :param DataModelEvent event:
    :param ctx:
    :return:
    """
    if 'pid' in event.state:
        criteria = Criteria(hostname=event.state.hostname, pid=event.state.pid)
        criteria.coalesce(event.state, 'image_path', 'exe', force=False)
        process_query = ProcessEvent.search(action='create', **criteria)

        for result in ctx.query(process_query, last=1, end=event.time + datetime.timedelta(minutes=1)):
            resolved_process = ProcessEvent.update_existing(action='create', **result)
            yield resolved_process
Exemplo n.º 5
0
def file_execution(file_event, ctx=None):
    """ Pivots from a dropped file onto a process start. Cases include running a
    new process (i.e. new_file.exe) and loading it as a script (i.e. powershell -ep bypass .\evil.ps1).
    """
    window = {'start': file_event.time - datetime.timedelta(minutes=1)}

    criteria = Criteria(hostname=file_event.state.hostname)

    if file_event.state.file_path:
        criteria['image_path'] = file_event.state.file_path
    elif file_event.state.file_name:
        criteria['exe'] = file_event.state.file_name

    for result in ctx.query(ProcessEvent.search(action='create', **criteria), **window):
        yield ProcessEvent.update_existing(action='create', **result)
Exemplo n.º 6
0
def wmic_process_create(process_event, ctx):
    """
    :param Process process_event: The input event to pivot on
    :param DataModelQueryLayer ctx: Search context to query over
    """

    if process_event.state.exe.lower() == "wmic.exe":
        args = command_to_argv(process_event.state.command_line)
        lower_args = [_.lower() for _ in args]

        if not ("process" in lower_args and "call" in lower_args
                and "create" in lower_args):
            return

        criteria = Criteria(
            parent_image_path="C:\\Windows\\System32\\wbem\\WmiPrvSE.exe")
        for arg in args:
            if arg.lower().startswith("/node:"):
                wmi_node = arg.split(":").pop().replace('"', '')
                host_info = extract_host(wmi_node)
                if 'hostname' in host_info:
                    criteria['hostname'] = host_info['hostname']
                break

        else:
            # if the system is not directly defined, then the host stays the same
            criteria['hostname'] = process_event.state.hostname

        wmic_command = next_arg(args, "create")
        if wmic_command is None:
            return

        # Get the case version of the command
        logger.debug("Pivoting onto WMIC command with command line {}".format(
            wmic_command))
        criteria['command_line'] = wmic_command

        for process_event in ctx.query(
                ProcessEvent.search('create', **criteria),
                start=process_event.time - datetime.timedelta(seconds=10),
                end=process_event.time + datetime.timedelta(minutes=1)):
            task_process = ProcessEvent.update_existing(
                'create', **process_event)
            yield task_process
Exemplo n.º 7
0
def lookup_parent(process_event, ctx=None):
    """ Given a process creation, try to find the parent process. In this case, pid reuse shouldn't be a problem.
    Unless there is a missing gap of data.
    :type process_event: ProcessEvent
    """

    # Get the time where the pid is reused (if it is)
    if 'ppid' in process_event.state:
        criteria = Criteria(pid=process_event.state.ppid, hostname=process_event.state.hostname)

        # If this event has the process field, then it is likely the next one will
        if process_event.state.parent_image_path:
            criteria['image_path'] = process_event.state.parent_image_path
        elif process_event.state.parent_exe:
            criteria['exe'] = process_event.state.parent_exe

        parent_query = ProcessEvent.search(action='create', **criteria)

        for result in ctx.query(parent_query, last=1, end=process_event.time + datetime.timedelta(minutes=1)):
            parent_process = ProcessEvent.update_existing(action='create', **result)
            yield parent_process
Exemplo n.º 8
0
def at(process_event, ctx):
    """ :type process_event: Process """

    if process_event.state.exe.lower() == "at.exe" and "/?" not in str(
            process_event.state.command_line):
        args = command_to_argv(process_event.state.command_line)
        lower_args = [_.lower() for _ in args]
        if len(args) == 1:
            return

        criteria = Criteria(
        )  # parent_exe="taskeng.exe". This may not be true in windows 8
        if len(args) > 2 and args[1].startswith("\\\\"):
            host_string = args[1]
            host_info = extract_host(host_string)
            if 'hostname' in host_info:
                criteria['hostname'] = host_info['hostname']

        else:
            # if the system is not directly defined, then the host stays the same
            criteria['hostname'] = process_event.state.hostname

        if lower_args[-1] in ('/delete', '/yes'):
            return

        # Get the case version of the command
        command = args[-1]
        logger.debug(
            "Pivoting onto AT command with command line {}".format(command))
        criteria['command_line'] = command

        query = ProcessEvent.search('create', **criteria) & (
            (FieldQuery("parent_image_path")
             == "C:\\Windows\\system32\\taskeng.exe") |
            (FieldQuery("parent_image_path")
             == "C:\\Windows\\system32\\svchost.exe"))
        for process_event in ctx.query(query, start=process_event.time):
            task_process = ProcessEvent.update_existing(
                'create', **process_event)
            yield task_process
Exemplo n.º 9
0
def schtasks(process_event, ctx):
    """ :type process_event: Process """
    if process_event.state.exe.lower() == "schtasks.exe" and "/tr" in str(
            process_event.state.command_line).lower():
        args = command_to_argv(process_event.state.command_line)
        lower_args = [_.lower() for _ in args]
        criteria = Criteria()

        # if scheduling a task on a remote system, extract out fqdn or hostname (but could be IP)
        if "/s" in lower_args:
            host = next_arg(args, "/s")
            if host:
                host_info = extract_host(host)
                if 'hostname' in host_info:
                    criteria['hostname'] = host_info['hostname']

        else:
            # if the system is not directly defined, then the host stays the same
            criteria['hostname'] = process_event.state.hostname

        # now extract out the task to be run from the command line
        task_run = next_arg(args, "/tr")
        if task_run is None:
            return

        logger.debug(
            "Pivoting onto scheduled task with command line {}".format(
                task_run))

        query = ProcessEvent.search('create', **criteria) & FieldQuery(
            'command_line').wildcard('*' + task_run + '*') & (
                (FieldQuery("parent_image_path")
                 == "C:\\Windows\\System32\\taskeng.exe") |
                (FieldQuery("parent_image_path")
                 == "C:\\Windows\\System32\\svchost.exe"))

        for process_event in ctx.query(query, start=process_event.time):
            task_process = ProcessEvent.update_existing(
                'create', **process_event)
            yield task_process
Exemplo n.º 10
0
def child_process(process_event, ctx=None):
    """ Pivots from the creation of a process, to all spawned child processes.
    :type process_event: ProcessEvent
    """
    window = {'start': process_event.time - datetime.timedelta(minutes=1)}
    if process_event.duration is not None:
        window['end'] = process_event.time + process_event.duration + datetime.timedelta(minutes=1)

    criteria = Criteria(ppid=process_event.state.pid, hostname=process_event.state.hostname)

    # try to filter this out further to make a more precise query
    if process_event.state.parent_image_path and process_event.state.image_path:
        criteria['parent_image_path'] = process_event.state.image_path
    elif process_event.state.parent_exe and process_event.state.exe:
        criteria['parent_exe'] = process_event.state.exe

    query = ctx.query(ProcessEvent.search(action='create', **criteria), **window)

    for result in query:
        child_event = ProcessEvent.update_existing(action='create', **result)
        child_event.parent = process_event
        child_event.save()
        yield child_event
Exemplo n.º 11
0
def injection_source(thread_event, ctx=None):
    """ Given a suspicious thread, determine the process(es) on each endpoint
    of the connection. This will resolve the processes, but will not look for
    creation or termination events. As a result, it will not pivot onto the
    launched child processes of the other endpoints. This would avoid investigating
    on the legitimate half a connection, but be informational in detecting the
    responsible process.
    """

    # Find the most recent creation event for this pid

    criteria = Criteria()

    if 'src_pid' in thread_event.state:
        criteria['hostname'] = thread_event.state.hostname
        src_fields = {
            k.split("src_").pop(): v
            for k, v in thread_event.state.to_mongo().items()
            if k.startswith("src_")
        }
        criteria.coalesce(src_fields, 'image_path', 'exe', force=False)
        criteria.coalesce(src_fields,
                          'parent_image_path',
                          'parent_exe',
                          force=False)
        criteria.coalesce(src_fields, 'ppid', force=False)

        parent_query = ProcessEvent.search(action='create', **criteria)

        for result in ctx.query(parent_query,
                                last=1,
                                end=thread_event.time +
                                datetime.timedelta(minutes=1)):
            resolved_process = ProcessEvent.update_existing(action='create',
                                                            **result)
            yield resolved_process
Exemplo n.º 12
0
def tainted_process(thread_event, ctx):
    """ Given a thread injection event, taint the process from that point forward.
    :type thread_event: Thread
    """
    # The process must have existed by the time the remote thread was created
    window = {
        'end': thread_event.time + datetime.timedelta(minutes=1),
        'last': 1
    }
    criteria = Criteria(pid=thread_event.state.target_pid)
    criteria['hostname'] = thread_event.state.hostname

    # filter down the process if possible
    if 'target_image_path' in thread_event.state:
        criteria['image_path'] = thread_event.state.target_image_path
    elif 'target_exe' in thread_event.state:
        criteria['exe'] = thread_event.state.target_exe

    query = ctx.query(ProcessEvent.search(action='create', **criteria),
                      **window)

    for result in query:
        # taint the process immediately before the injection occurred (to be safe)
        result['time'] = thread_event.time - datetime.timedelta(seconds=1)
        injected_process = ProcessEvent.update_existing(action='inject',
                                                        **result)

        # Once the creation event has been detected, update the injection event with the target information
        # from the target process
        for field in injected_process.state:
            thread_field = 'target_' + field
            if thread_field in ThreadEvent.fields and thread_field not in thread_event.state:
                thread_event.state[thread_field] = injected_process.state[
                    field]
            thread_event.save()

        injected_process.save()
        yield injected_process
        break

    # However, if the process creation can not be found, then make up an injection event
    else:
        process_info = {}
        if thread_event.state.fqdn:
            process_info['fqdn'] = thread_event.state.fqdn
        if thread_event.state.hostname:
            process_info['hostname'] = thread_event.state.hostname

        for field in ProcessEvent.fields:
            thread_field = 'target_' + field
            if thread_field in thread_event.state:
                process_info[field] = thread_event.state[thread_field]

        inject_time = thread_event.time
        fake_injected = ProcessEvent.update_existing(
            action='inject',
            metadata={'synthetic': True},
            state=process_info,
            time=inject_time)
        logger.warning(
            'Process create not found. Fabricating injection event!')
        yield fake_injected