Example #1
0
    def run(self):
        self.nitro = Nitro(self.domain, self.analyze_enabled)
        self.nitro.listener.set_traps(True)
        for event in self.nitro.listen():
            event_info = event.as_dict()
            if self.analyze_enabled:
                try:
                    syscall = self.nitro.backend.process_event(event)
                except LibvmiError:
                    logging.error("Backend event processing failure")
                else:
                    event_info = syscall.as_dict()
            if self.output is None:
                pprint(event_info, width=1)
            else:
                self.events.append(event_info)

        if self.analyze_enabled:
            # we can safely stop the backend
            self.nitro.backend.stop()

        if self.output is not None:
            logging.info('Writing events')
            with open(self.output, 'w') as f:
                json.dump(self.events, f, indent=4)
Example #2
0
class NitroThread(Thread):
    def __init__(self,
                 domain,
                 analyze=False,
                 enter_hooks=None,
                 exit_hooks=None,
                 ready_event=None):
        super().__init__()
        self.domain = domain
        self.analyze_enabled = analyze
        self.enter_hooks = enter_hooks or {}
        self.exit_hooks = exit_hooks or {}
        self.stop_request = Event()
        self.total_time = None
        self.events = []
        self.ready_event = ready_event
        self.nitro = Nitro(self.domain, self.analyze_enabled)

    def run(self):
        # start timer
        start_time = datetime.datetime.now()
        if self.analyze_enabled:
            for name, callback in self.enter_hooks.items():
                self.nitro.backend.define_hook(
                    name, callback, direction=SyscallDirection.enter)
            for name, callback in self.exit_hooks.items():
                self.nitro.backend.define_hook(name,
                                               callback,
                                               direction=SyscallDirection.exit)
        self.nitro.listener.set_traps(True)
        if self.ready_event is not None:
            self.ready_event.set()  # is this really necessary
        for event in self.nitro.listen():
            if self.analyze_enabled:
                try:
                    syscall = self.nitro.backend.process_event(event)
                except LibvmiError:
                    ev_info = event.as_dict()
                else:
                    ev_info = syscall.as_dict()
            else:
                ev_info = event.as_dict()
            self.events.append(ev_info)

        # stop timer
        self.ready_event.clear()
        stop_time = datetime.datetime.now()
        self.total_time = str(stop_time - start_time)

    def stop(self):
        self.nitro.stop()
        self.join()
Example #3
0
 def __init__(self,
              domain,
              analyze=False,
              enter_hooks=None,
              exit_hooks=None,
              ready_event=None):
     super().__init__()
     self.domain = domain
     self.analyze_enabled = analyze
     self.enter_hooks = enter_hooks or {}
     self.exit_hooks = exit_hooks or {}
     self.stop_request = Event()
     self.total_time = None
     self.events = []
     self.ready_event = ready_event
     self.nitro = Nitro(self.domain, self.analyze_enabled)
Example #4
0
def main(args):
    vm_name = args['<vm_name>']
    # get domain from libvirt
    con = libvirt.open('qemu:///system')
    domain = con.lookupByName(vm_name)

    events = []
    # init backend if necessary
    backend = None
    if not args['--nobackend']:
        backend = Backend(domain)

    # start Nitro
    with Nitro(domain) as nitro:
        nitro.set_traps(True)
        for event in nitro.listen():
            ev_info = None
            if backend:
                syscall = backend.process_event(event)
                ev_info = syscall.info()
            else:
                ev_info = event.info()
            if args['--stdout']:
                pprint(ev_info, width=1)
            else:
                events.append(ev_info)

            # stop properly by CTRL+C
            if not run:
                break

    if events:
        logging.info('Writing events')
        with open('events.json', 'w') as f:
            json.dump(events, f)
Example #5
0
class NitroRunner:
    def __init__(self, vm_name, analyze_enabled, output=None):
        self.vm_name = vm_name
        self.analyze_enabled = analyze_enabled
        self.output = output
        # get domain from libvirt
        con = libvirt.open('qemu:///system')
        self.domain = con.lookupByName(vm_name)
        self.events = []
        self.nitro = None
        # define new SIGINT handler, to stop nitro
        signal.signal(signal.SIGINT, self.sigint_handler)

    def run(self):
        self.nitro = Nitro(self.domain, self.analyze_enabled)
        self.nitro.listener.set_traps(True)
        for event in self.nitro.listen():
            event_info = event.as_dict()
            if self.analyze_enabled:
                try:
                    syscall = self.nitro.backend.process_event(event)
                except LibvmiError:
                    logging.error("Backend event processing failure")
                else:
                    event_info = syscall.as_dict()
            if self.output is None:
                pprint(event_info, width=1)
            else:
                self.events.append(event_info)

        if self.analyze_enabled:
            # we can safely stop the backend
            self.nitro.backend.stop()

        if self.output is not None:
            logging.info('Writing events')
            with open(self.output, 'w') as f:
                json.dump(self.events, f, indent=4)

    def sigint_handler(self, *args, **kwargs):
        logging.info('CTRL+C received, stopping Nitro')
        self.nitro.stop()
Example #6
0
def run_nitro_thread(domain, stop_request):
    nb_syscalls = 0
    with Nitro(domain) as nitro:
        nitro.set_traps(True)
        logging.info('Counting syscalls...')
        for event in nitro.listen():
            if event.direction == SyscallDirection.enter:
                nb_syscalls += 1
            if stop_request.isSet():
                break
    logging.info('Nb Syscalls : {}'.format(nb_syscalls))
Example #7
0
 def __init__(self, domain, analyze=False):
     self.domain = domain
     self.analyze = analyze
     # init Nitro
     self.nitro = Nitro(self.domain)
     if analyze:
         vcpus_info = self.domain.vcpus()
         self.nb_vcpu = len(vcpus_info[0])
         # create on syscall stack per vcpu
         self.syscall_stack = {}
         for vcpu_nb in range(self.nb_vcpu):
             self.syscall_stack[vcpu_nb] = []
         self.sdt = None
         self.load_symbols()
         # run libvmi helper subprocess
         self.libvmi = Libvmi(domain.name())
         self.processes = {}
         self.hooks = {
             SyscallDirection.enter: {},
             SyscallDirection.exit: {}
         }
         self.stats = defaultdict(int)
Example #8
0
from nitro.nitro import Nitro
from nitro.libvmi import LibvmiError

with Nitro("Windows-VM", introspection=True) as nitro:
    self.nitro.listener.set_traps(True)
    for event in nitro.listen():
        try:
            syscall = nitro.backend.process_event(event)
        except LibvmiError:
            print("Failed to analyze event :/")
        else:
            if syscall.process.name == "notepad.exe":
                print(syscall.as_dict())
Example #9
0
    def run(self):
        # ffff880074c13c74
        print("Wait for setup rekall and nitro ...")
        self.nitro = Nitro(self.domain, self.analyze_enabled)
        self.ox = OX(self.nitro)
        while True:
            print("Which program you want to execute:")
            print("---- help:")
            print("---- kill [pid]")
            print("---- write [vaddr] [optional value]")
            print("---- nop")
            print("---- exit")
            print("---- statistics")

            callback = None
            is_statistics = False

            cmd = input("> ")
            command = cmd.split()

            print(command)
            if command[0].lower() not in [
                    "kill", "write", "exit", "nop", "statistics"
            ]:
                print("Program not found")
                continue

            if command[0].lower() == "exit":
                print("Bye!")
                break
            try:
                if command[0].lower() == "statistics":
                    is_statistics = True
                if command[0].lower() == "nop":
                    callback = NOPCallback(self.ox).do_callback

                if command[0].lower() == "kill":
                    if len(command) != 2:
                        print("PID is needed for kill program")
                        continue
                    else:
                        callback = KillCallback(self.ox,
                                                int(command[1])).do_callback

                if command[0].lower() == "write":
                    if len(command) == 2:
                        callback = WriteCallback(self.ox, int(command[1],
                                                              0)).do_callback
                    elif len(command) == 3:
                        callback = WriteCallback(self.ox, int(command[1], 0),
                                                 int(command[2])).do_callback
                    else:
                        print("vaddr is needed for write program")
                        continue

                loop_nr = int(
                    input(
                        "> How many times do you want to execute this program[defualt: 1]: "
                    ) or "1")
                sleep_time = float(
                    input(
                        "> How much do you want to sleep between each run[defualt: 0.1]: "
                    ) or "0.1")
                delay_file_output = input("> Save results in which file: ")

            except ValueError as e:
                print(e)
                print("Value error. Please enter a valid value")
                continue
            self.is_busy = 0
            busy_times = 0.0
            total_times = 0.0

            # callback()
            for i in range(0, loop_nr):
                print("#{}".format(i))
                # self.start = timer()
                try:
                    #     loop = asyncio.get_event_loop()
                    #     self.end = loop.run_until_complete(asyncio.wait_for(
                    #         self.ox.begin_transaction(callback, loop), 3)
                    #     )
                    #     for signame in ('SIGINT', 'SIGTERM'):
                    #         loop.add_signal_handler(getattr(signal, signame),
                    #                                 functools.partial(self.ox.run_rollback))
                    #
                    #     loop.close()
                    if is_statistics:
                        self.is_busy = self.ox.get_statistic()
                        self.end = 1
                        if self.is_busy:
                            busy_times += 1
                        total_times += 1
                    else:
                        self.end, self.start = self.ox.sync_begin_transaction(
                            callback)
                except Exception as e:
                    traceback.print_exc()
                    print("Oxpecker got exception.")
                    self.end = 0
                    self.is_busy = 0
                finally:
                    if self.nitro.listener.current_cont_event:
                        self.nitro.listener.stop_listen()
                # self.is_busy = self.ox.get_statistic()
                # self.nitro.listener.stop()
                is_write_to_output = self.end != 0
                if delay_file_output and is_write_to_output:
                    row = [self.is_busy] if is_statistics else [
                        self.start, self.end, self.end - self.start
                    ]
                    with open(delay_file_output, "a") as f:
                        writer = csv.writer(f)
                        writer.writerow(row)
                sleep(sleep_time)
            if is_statistics:
                print('busy times: {}, total_times:{}'.format(
                    busy_times, total_times))
                print(
                    'The percentage of idle time: {}'.format(1.0 - busy_times /
                                                             total_times))

        self.nitro.listener.close_fd()
Example #10
0
class Backend:

    __slots__ = (
        'domain',
        'nb_vcpu',
        'syscall_stack',
        'sdt',
        'libvmi',
        'processes',
        'hooks',
        'stats',
        'nitro',
        'analyze',
        'symbols'
    )

    def __init__(self, domain, analyze=False):
        self.domain = domain
        self.analyze = analyze
        # init Nitro
        self.nitro = Nitro(self.domain)
        if analyze:
            vcpus_info = self.domain.vcpus()
            self.nb_vcpu = len(vcpus_info[0])
            # create on syscall stack per vcpu
            self.syscall_stack = {}
            for vcpu_nb in range(self.nb_vcpu):
                self.syscall_stack[vcpu_nb] = []
            self.sdt = None
            self.load_symbols()
            # run libvmi helper subprocess
            self.libvmi = Libvmi(domain.name())
            self.processes = {}
            self.hooks = {
                SyscallDirection.enter: {},
                SyscallDirection.exit: {}
            }
            self.stats = defaultdict(int)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.stop()

    def stop(self):
        if self.analyze:
            # print stats if some errors happened
            if self.stats:
                logging.info(json.dumps(self.stats, indent=4))
            self.libvmi.destroy()
        self.nitro.stop()

    def load_symbols(self):
        # we need to put the ram dump in our own directory
        # because otherwise it will be created in /tmp
        # and later owned by root
        with TemporaryDirectory() as tmp_dir:
            with NamedTemporaryFile(dir=tmp_dir) as ram_dump:
                # chmod to be r/w by everyone
                os.chmod(ram_dump.name, stat.S_IRUSR | stat.S_IWUSR |
                                        stat.S_IRGRP | stat.S_IWGRP |
                                        stat.S_IROTH | stat.S_IWOTH)
                # take a ram dump
                logging.info('Dumping physical memory to {}'.format(ram_dump.name))
                flags = libvirt.VIR_DUMP_MEMORY_ONLY
                dumpformat = libvirt.VIR_DOMAIN_CORE_DUMP_FORMAT_RAW
                self.domain.coreDumpWithFormat(ram_dump.name, dumpformat, flags)
                # build symbols.py absolute path
                script_dir = os.path.dirname(os.path.realpath(__file__))
                symbols_script_path = os.path.join(script_dir, GETSYMBOLS_SCRIPT)
                # call rekall on ram dump
                logging.info('Extracting symbols with Rekall')
                python2 = shutil.which('python2')
                symbols_process = [python2, symbols_script_path, ram_dump.name]
                output = subprocess.check_output(symbols_process)
        logging.info('Loading symbols')
        # load output as json
        symbols = json.loads(output.decode('utf-8'))
        # load ssdt entries
        nt_ssdt = {'ServiceTable': {}, 'ArgumentTable': {}}
        win32k_ssdt = {'ServiceTable': {}, 'ArgumentTable': {}}
        self.sdt = [nt_ssdt, win32k_ssdt]
        cur_ssdt = None
        for e in symbols['syscall_table']:
            if isinstance(e, list) and e[0] == 'r':
                if e[1]["divider"] is not None:
                    # new table
                    m = re.match(r'Table ([0-9]) @ .*', e[1]["divider"])
                    idx = int(m.group(1))
                    cur_ssdt = self.sdt[idx]['ServiceTable']
                else:
                    entry = e[1]["entry"]
                    full_name = e[1]["symbol"]["symbol"]
                    # add entry  to our current ssdt
                    cur_ssdt[entry] = full_name
                    logging.debug('Add SSDT entry [{}] -> {}'.format(entry, full_name))
        # save rekall symbols
        self.symbols = symbols

    def process_event(self, event):
        if not self.analyze:
            raise RuntimeError('Syscall analyzing is disabled in the backend')
        # invalidate libvmi cache
        self.libvmi.v2pcache_flush()
        self.libvmi.pidcache_flush()
        self.libvmi.rvacache_flush()
        self.libvmi.symcache_flush()
        # rebuild context
        cr3 = event.sregs.cr3
        # 1 find process
        process = self.associate_process(cr3)
        # 2 find syscall
        if event.direction == SyscallDirection.exit:
            try:
                syscall = self.syscall_stack[event.vcpu_nb].pop()
                # replace register values
                syscall.event = event
            except IndexError:
                # build a new syscall object using 'Unknown' as syscall name
                syscall = Syscall(event, 'Unknown', process, self.nitro)
        else:
            syscall_name = self.get_syscall_name(event.regs.rax)
            # build syscall
            syscall = Syscall(event, syscall_name, process, self.nitro)
            # push syscall to the stack to retrieve it at exit
            self.syscall_stack[event.vcpu_nb].append(syscall)
        # dispatch on the hooks
        self.dispatch_hooks(syscall)
        return syscall

    def dispatch_hooks(self, syscall):
        try:
            hook = self.hooks[syscall.event.direction][syscall.name]
        except KeyError:
            pass
        else:
            try:
                logging.debug('Processing hook {} - {}'.format(syscall.event.direction.name, hook.__name__))
                hook(syscall, self)
            except InconsistentMemoryError:
                self.stats['memory_access_error'] += 1
                logging.exception('Memory access error')
            except LibvmiError:
                self.stats['libvmi_failure'] += 1
                logging.exception('VMI_FAILURE')
            # misc failures
            except ValueError:
                self.stats['misc_error'] += 1
                logging.exception('Misc error')
            except Exception:
                logging.exception('Unknown error while processing hook')
            else:
                self.stats['hooks_completed'] += 1
            finally:
                self.stats['hooks_processed'] += 1

    def define_hook(self, name, callback, direction=SyscallDirection.enter):
        logging.info('Defining hook on {}'.format(name))
        self.hooks[direction][name] = callback

    def undefine_hook(self, name, direction=SyscallDirection.enter):
        logging.info('Removing hook on {}'.format(name))
        self.hooks[direction].pop(name)

    def associate_process(self, cr3):
        try:
            p = self.processes[cr3]
        except KeyError:
            p = self.find_eprocess(cr3)
            # index by cr3 or pid
            # a callback might want to search by pid
            self.processes[cr3] = p
            self.processes[p.pid] = p
        return p

    def find_eprocess(self, cr3):
        # read PsActiveProcessHead list_entry
        ps_head = self.libvmi.translate_ksym2v('PsActiveProcessHead')
        flink = self.libvmi.read_addr_ksym('PsActiveProcessHead')

        while flink != ps_head:
            # get start of EProcess
            start_eproc = flink - self.symbols['offsets']['EPROCESS']['ActiveProcessLinks']
            # move to start of DirectoryTableBase
            directory_table_base_off = start_eproc + self.symbols['offsets']['KPROCESS']['DirectoryTableBase']
            # read directory_table_base
            directory_table_base = self.libvmi.read_addr_va(directory_table_base_off, 0)
            # compare to our cr3
            if cr3 == directory_table_base:
                eprocess = Process(cr3, start_eproc, self.libvmi, self.symbols)
                return eprocess

            # read new flink
            flink = self.libvmi.read_addr_va(flink, 0)
        raise RuntimeError('Process not found')

    def get_syscall_name(self, rax):
        ssn = rax & 0xFFF
        idx = (rax & 0x3000) >> 12
        try:
            syscall_name = self.sdt[idx]['ServiceTable'][ssn]
        except (KeyError, IndexError):
            # this code should not be reached, because there is only 2 SSDT's defined in Windows (Nt and Win32k)
            # the 2 others are NULL
            syscall_name = 'Table{}!Unknown'.format(idx)
        return syscall_name