# openioc_scan Volatility plugin
# based on ioc_writer (https://github.com/mandiant/ioc_writer) and pyioc (https://github.com/jeffbryner/pyioc)
# Copyright (c) 2014 Takahiro Haruyama (@cci_forensics)
# http://takahiroharuyama.github.io/

import volatility.utils as utils
import volatility.obj as obj
import volatility.debug as debug
import volatility.constants as constants
import volatility.commands as commands

import volatility.plugins.common as common
import volatility.plugins.netscan as netscan
import volatility.plugins.overlays.windows.tcpip_vtypes as tcpip_vtypes
import volatility.plugins.registry.hivelist as hivelist
import volatility.plugins.registry.shimcache as shimcache
import volatility.plugins.taskmods as taskmods
import volatility.plugins.modules as modules
import volatility.plugins.modscan as modscan
import volatility.plugins.filescan as filescan
import volatility.plugins.privileges as privileges
import volatility.plugins.ssdt as ssdt
import volatility.plugins.mftparser as mftparser
import volatility.plugins.malware.malfind as malfind
import volatility.plugins.malware.impscan as impscan
import volatility.plugins.malware.psxview as psxview
import volatility.plugins.malware.svcscan as svcscan
import volatility.plugins.malware.apihooks as apihooks
import volatility.plugins.malware.devicetree as devicetree
import volatility.plugins.malware.callbacks as callbacks
import volatility.plugins.malware.timers as timers

import volatility.win32 as win32
import volatility.win32.hive as hivemod
import volatility.win32.rawreg as rawreg
import volatility.win32.tasks as tasks

import glob, os, re, sqlite3, urllib, socket, time
from lxml import etree as et
from ioc_writer import ioc_api
import colorama
colorama.init()

g_version = '2015/02/24'
g_cache_path = ''
g_detail_on = False
g_color_term = colorama.Fore.MAGENTA
g_color_detail = colorama.Fore.CYAN
g_sus_path_p = re.compile(r'\\ProgramData|\\\$Recycle\.Bin|\\Windows\\Temp|\\Users\\All Users|\\Users\\Default|\\Users\\Public|\\Users\\.*\\AppData', re.IGNORECASE)
READ_BLOCKSIZE = 1024 * 1024 * 10
SCORE_THRESHOLD = 100

# copied from netscan
AF_INET = 2
AF_INET6 = 0x17
inaddr_any = utils.inet_ntop(socket.AF_INET, '\0' * 4)
inaddr6_any = utils.inet_ntop(socket.AF_INET6, '\0' * 16)

if constants.VERSION < 2.4:
    # copied from malfind
    class MalwareObjectClases(obj.ProfileModification):
        before = ['WindowsObjectClasses']
        conditions = {'os': lambda x: x == 'windows'}
        def modification(self, profile):
            profile.object_classes.update({
                '_EPROCESS': malfind.MalwareEPROCESS,
            })

# copied from apihooks
# hook modes
HOOK_MODE_USER = 1
HOOK_MODE_KERNEL = 2
# hook types
HOOKTYPE_IAT = 4
HOOKTYPE_EAT = 8
HOOKTYPE_INLINE = 16
HOOKTYPE_NT_SYSCALL = 32
HOOKTYPE_CODEPAGE_KERNEL = 64
HOOKTYPE_IDT = 128
HOOKTYPE_IRP = 256
HOOKTYPE_WINSOCK = 512
# names for hook types
hook_type_strings = apihooks.hook_type_strings
WINSOCK_TABLE = apihooks.WINSOCK_TABLE

# copied from devicetree
MAJOR_FUNCTIONS = devicetree.MAJOR_FUNCTIONS

# copied from privileges
PRIVILEGE_INFO = privileges.PRIVILEGE_INFO

class Timer(object):
    def __init__(self, verbose=False):
        self.verbose = verbose

    def __enter__(self):
        self.start = time.time()
        return self

    def __exit__(self, *args):
        self.end = time.time()
        self.secs = self.end - self.start
        self.msecs = self.secs * 1000  # millisecs
        if self.verbose:
            print 'elapsed time: %f ms' % self.msecs

class ItemUtil:
    def is_condition_bool(self, condition):
        supported_conditions = ['is', 'contains']
        if condition in supported_conditions:
            return True
        else:
            return False

    def is_condition_string(self, condition):
        supported_conditions = ['is', 'contains', 'matches', 'starts-with', 'ends-with']
        if condition in supported_conditions:
            return True
        else:
            return False

    def is_condition_integer(self, condition):
        supported_conditions = ['is', 'greater-than', 'less-than']
        if condition in supported_conditions:
            return True
        else:
            return False

    def make_regex(self, content, preserve_case):
        if preserve_case == 'true':
            pattern = re.compile(content, re.DOTALL)
        else:
            pattern = re.compile(content, re.DOTALL | re.IGNORECASE)
        return pattern

    def check_string(self, target, content, condition, preserve_case):
        #out = colorama.Style.BRIGHT + g_color_detail + target + colorama.Fore.RESET + colorama.Style.RESET_ALL
        out = g_color_detail + target + colorama.Fore.RESET
        if condition == 'matches':
            pattern = self.make_regex(content, preserve_case)
            if pattern.search(target) is not None:
                if g_detail_on:
                    print('matched IOC term detail: {0}'.format(out))
                return True
        else:
            if preserve_case == 'false':
                target = target.lower()
                content = content.lower()
            if condition == 'is':
                if target == content:
                    if g_detail_on:
                        print('matched IOC term detail: {0}'.format(out))
                    return True
            elif condition == 'contains':
                if target.find(content) != -1:
                    if g_detail_on:
                        print('matched IOC term detail: {0}'.format(out))
                    return True
            elif condition == 'starts-with':
                if target.startswith(content):
                    if g_detail_on:
                        print('matched IOC term detail: {0}'.format(out))
                    return True
            elif condition == 'ends-with':
                if target.endswith(content):
                    if g_detail_on:
                        print('matched IOC term detail: {0}'.format(out))
                    return True
        return False

    def check_strings(self, target_list, content, condition, preserve_case):
        result = False
        for target in target_list:
            if self.check_string(target, content, condition, preserve_case):
                #return True
                result = True
        #return False
        return result

    def extract_unicode(self, data):
        pat = re.compile(ur'(?:[\x20-\x7E][\x00]){4,}')
        return list(set([w.decode('utf-16le') for w in pat.findall(data)]))

    def extract_ascii(self, data):
        pat = re.compile(r'(?:[\x20-\x7E]){4,}')
        return list(set([w.decode('ascii') for w in pat.findall(data)]))

    def check_integer(self, target, content, condition, preserve_case):
        if condition == 'is':
            if target == content:
                return True
        elif condition == 'greater-than':
            if target > content:
                return True
        elif condition == 'less-than':
            if target < content:
                return True
        return False

    def check_integers(self, target_list, content, condition, preserve_case):
        for target in target_list:
            if self.check_integer(target, content, condition, preserve_case):
                return True
        return False

    def fetchall_from_db(self, cur, table, column):
        debug.debug("{0} already done. Results reused".format(table))
        sql = "select {0} from {1}".format(column, table)
        cur.execute(sql)
        return [record[0] for record in cur.fetchall()]

    def fetchone_from_db(self, cur, table, column):
        debug.debug("{0} already done. Results reused".format(table))
        sql = "select {0} from {1}".format(column, table)
        cur.execute(sql)
        return cur.fetchone()[0]

class ProcessItem(impscan.ImpScan, netscan.Netscan, malfind.Malfind, apihooks.ApiHooks):
    def __init__(self, process, cur, _config):
        self.process = process
        self.cur = cur
        self._config = _config
        self.kernel_space = utils.load_as(self._config)
        self.flat_space = utils.load_as(self._config, astype = 'physical')
        self.forwarded_imports = { # copied from impscan
            "RtlGetLastWin32Error" : "kernel32.dll!GetLastError",
            "RtlSetLastWin32Error" : "kernel32.dll!SetLastError",
            "RtlRestoreLastWin32Error" : "kernel32.dll!SetLastError",
            "RtlAllocateHeap" : "kernel32.dll!HeapAlloc",
            "RtlReAllocateHeap" : "kernel32.dll!HeapReAlloc",
            "RtlFreeHeap" : "kernel32.dll!HeapFree",
            "RtlEnterCriticalSection" : "kernel32.dll!EnterCriticalSection",
            "RtlLeaveCriticalSection" : "kernel32.dll!LeaveCriticalSection",
            "RtlDeleteCriticalSection" : "kernel32.dll!DeleteCriticalSection",
            "RtlZeroMemory" : "kernel32.dll!ZeroMemory",
            "RtlSizeHeap" : "kernel32.dll!HeapSize",
            "RtlUnwind" : "kernel32.dll!RtlUnwind",
            }
        self.util = ItemUtil()
        self.compiled_rules = self.compile()

    def read_without_zero_page(self, vad, address_space):
        PAGE_SIZE = 0x1000
        all_zero_page = "\x00" * PAGE_SIZE

        offset = 0
        data = ''
        while offset < vad.Length:
            next_addr = vad.Start + offset
            if address_space.is_valid_address(next_addr):
                page = address_space.read(next_addr, PAGE_SIZE)
                if page != all_zero_page:
                    data += page
            offset += PAGE_SIZE
        return data

    def check_done(self, item):
        sql = "select {0} from done where pid = ?".format(item)
        self.cur.execute(sql, (self.process.UniqueProcessId.v(),))
        return self.cur.fetchone()

    def update_done(self, item):
        sql = "update done set {0} = ? where pid = ?".format(item)
        self.cur.execute(sql, (True, self.process.UniqueProcessId.v()))

    def update_all_done(self, item):
        sql = "update done set {0} = ?".format(item)
        self.cur.execute(sql, (True, ))

    def fetchall_from_db_by_pid(self, table, column):
        debug.debug("{0} already done. Results reused (pid={1})".format(table, self.process.UniqueProcessId))
        sql = "select {0} from {1} where pid = ?".format(column, table)
        self.cur.execute(sql, (self.process.UniqueProcessId.v(),))
        records = self.cur.fetchall()
        if records is None:
            return []
        return [record[0] for record in records]

    def fetchone_from_db_by_pid(self, table, column):
        debug.debug("{0} already done. Results reused (pid={1})".format(table, self.process.UniqueProcessId))
        sql = "select {0} from {1} where pid = ?".format(column, table)
        self.cur.execute(sql, (self.process.UniqueProcessId.v(),))
        record = self.cur.fetchone()
        if record is None: # for cmdLine
            return ''
        return record[0]

    def detect_code_injections(self):
        injected = []
        debug.info("[time-consuming task] detecting code injections...(pid={0})".format(self.process.UniqueProcessId))
        for vad, address_space in self.process.get_vads(vad_filter = self.process._injection_filter):
            if self._is_vad_empty(vad, address_space):
                continue
            self.cur.execute("insert into injected values (?, ?, ?)", (self.process.UniqueProcessId.v(), vad.Start, vad.Length))
            injected.append([vad.Start, vad.Length])
        self.update_done('injected')
        return injected

    def SectionList_MemorySection_Injected(self, content, condition, preserve_case):
        if not self.util.is_condition_bool(condition):
            debug.error('{0} condition is not supported in ProcessItem/SectionList/MemorySection/Injected'.format(condition))
            return False

        (done,) = self.check_done('injected')
        if int(done):
            counts = self.fetchone_from_db_by_pid('injected', 'count(*)')
        else:
            counts = len(self.detect_code_injections())

        if (counts > 0 and content.lower() == 'true') or (counts == 0 and content.lower() == 'false'):
            return True
        else:
            return False

    def SectionList_MemorySection_InjectedHexPattern(self, content, condition, preserve_case):
        if condition != 'matches':
            debug.error('{0} condition is not supported in ProcessItem/SectionList/MemorySection/InjectedHexPattern'.format(condition))
            return False

        (done,) = self.check_done('injected')
        if int(done):
            starts = self.fetchall_from_db_by_pid('injected', 'start')
        else:
            starts = [start for (start, size) in self.detect_code_injections()]

        pattern = self.util.make_regex(content, preserve_case)
        addr_space = self.process.get_process_address_space()
        for start in starts:
            content = addr_space.zread(start, 256)
            if pattern.search(content) is not None:
                return True
        return False

    def extract_strings(self):
        debug.info("[time-consuming task] extracting strings from VADs (pid={0})".format(self.process.UniqueProcessId))
        strings = []

        for vad, address_space in self.process.get_vads(skip_max_commit = True):
            data = self.read_without_zero_page(vad, address_space)
            if len(data) == 0:
                continue
            elif len(data) > READ_BLOCKSIZE:
                debug.debug('data size in VAD is more than READ_BLOCKSIZE (pid{0})'.format(self.process.UniqueProcessId))
            extracted = list(set(self.util.extract_unicode(data) + self.util.extract_ascii(data)))
            strings.extend(extracted)

        records = ((self.process.UniqueProcessId.v(), string) for string in strings)
        self.cur.executemany("insert or ignore into strings values (?, ?)", records)
        self.update_done('strings')
        return strings

    def check_and_extract_strings(self, content, condition, preserve_case):
        (done,) = self.check_done('strings')
        if int(done):
            strings = self.fetchall_from_db_by_pid('strings', 'string')
        else:
            strings = self.extract_strings()
        return self.util.check_strings(strings, content, condition, preserve_case)

    def StringList_string(self, content, condition, preserve_case):
        '''
        condition: is/contains/matches(regex)/starts-with/ends-with
        preserve_case: true/false
        '''
        result = False

        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/StringList/string'.format(condition))
            return False

        result = self.check_and_extract_strings(content, condition, preserve_case)
        if result == False and condition == 'matches': # for searching binary sequences
            pattern = self.util.make_regex(content, preserve_case)
            (done,) = self.check_done('vaddump')
            if int(done):
                debug.debug("vaddump already done. Results reused (pid={0})".format(self.process.UniqueProcessId))
                f = open(os.path.join(g_cache_path, 'vaddump_pid' + str(self.process.UniqueProcessId)) + '.bin', 'rb')

                i = 0
                overlap = 1024
                self.cur.execute("select size from vaddump where pid = ?", (self.process.UniqueProcessId.v(),))
                maxlen = self.cur.fetchone()[0]
                while i < maxlen:
                    to_read = min(READ_BLOCKSIZE + overlap, maxlen - i)
                    f.seek(i)
                    data = f.read(to_read)
                    if data:
                        if pattern.search(data) is not None:
                            return True
                    i += READ_BLOCKSIZE
                return False

            debug.info("[time-consuming task] dumping VADs for regex search... (pid={0})".format(self.process.UniqueProcessId))
            f = open(os.path.join(g_cache_path, 'vaddump_pid' + str(self.process.UniqueProcessId)) + '.bin', 'wb')
            size = 0
            for vad, address_space in self.process.get_vads(skip_max_commit = True):
                data = self.read_without_zero_page(vad, address_space)
                if len(data) == 0:
                    continue
                elif len(data) > READ_BLOCKSIZE:
                    debug.debug('data size in VAD is more than READ_BLOCKSIZE (pid{0})'.format(self.process.UniqueProcessId))
                if pattern.search(data) is not None:
                    result = True
                f.write(data)
                size += len(data)
            f.flush()
            f.close()
            self.cur.execute("insert into vaddump values (?, ?)", (self.process.UniqueProcessId.v(), size))
            self.update_done('vaddump')

        return result

    # based on impscan (overrided)
    def _vicinity_scan(self, addr_space, calls_imported, apis, base_address, data_len, forward, injected):

        sortedlist = calls_imported.keys()
        sortedlist.sort()

        debug.debug('_vicinity_scan: base={0:x}'.format(base_address))
        if not sortedlist:
            debug.debug('sortedlist:None')
            return

        size_of_address = addr_space.profile.get_obj_size("address")

        if forward:
            start_addr = sortedlist[0]
        else:
            start_addr = sortedlist[len(sortedlist) - 1]

        if injected:
            # searching dynamically generated IAT
            threshold = 0x400
            if not forward:
                start_addr += 0x1000
        else:
            threshold = 5
        i = 0

        while threshold and i < 0x2000:
            if forward:
                next_addr = start_addr + (i * size_of_address)
            else:
                next_addr = start_addr - (i * size_of_address)

            debug.debug('next_addr {0:x} (threshold={1})'.format(next_addr, threshold))
            call_dest = obj.Object("address", offset = next_addr,
                            vm = addr_space).v()

            #if (not call_dest or call_dest < base_address or call_dest > base_address + data_len): <- original code miss the entries
            if (not call_dest or (call_dest > base_address and call_dest < base_address + data_len)):
                debug.debug('continued {0:x}:{1:x}'.format(next_addr, call_dest))
                threshold -= 1
                i += 1
                continue

            if call_dest in apis and call_dest not in calls_imported:
                debug.debug('found {0:x}:{1:x}'.format(next_addr, call_dest))
                calls_imported[next_addr] = call_dest
                if injected:
                    threshold = 0x400
                else:
                    threshold = 5
            else:
                threshold -= 1

            i += 1

    # based on impscan
    def SectionList_MemorySection_PEInfo_ImportedModules_Module_ImportedFunctions_string(self, content, condition, preserve_case):
        result = False

        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/SectionList/MemorySection/PEInfo/ImportedModules/Module/ImportedFunctions/string'.format(condition))
            return False

        (done,) = self.check_done('impfunc')
        if int(done):
            imp_funcs = self.fetchall_from_db_by_pid('impfunc', 'func_name')
            return self.util.check_strings(imp_funcs, content, condition, preserve_case)

        debug.info("[time-consuming task] extracting imported functions...(pid={0})".format(self.process.UniqueProcessId))
        if self.process._vol_vm == self.flat_space:
            debug.warning('This process (pid={0}) seems to be dead. Skipping extraction of imported functions..'.format(self.process.UniqueProcessId))
            self.update_done('impfunc')
            return False

        scan_list = []
        all_mods = list(self.process.get_load_modules())
        if all_mods is not None and len(all_mods) > 0:
            # add the process image region
            scan_list.append((all_mods[0].DllBase, all_mods[0].SizeOfImage, False)) # start, size, injected

        # add suspicious DLL regions based on ldrmodules
        p = re.compile(r'')
        for vad, address_space in self.process.get_vads(vad_filter = self.process._mapped_file_filter):
            if obj.Object("_IMAGE_DOS_HEADER", offset = vad.Start, vm = address_space).e_magic != 0x5A4D:
                continue
            path = str(vad.FileObject.FileName or 'none').lower()
            if g_sus_path_p.search(path) is not None:
                entry = (vad.Start, vad.Length, False)
                if entry not in scan_list: # exclude exe
                    debug.info('add suspicious dll to scan_list: {0}'.format(path))
                    scan_list.append(entry)

        # add injected memory regions
        (done,) = self.check_done('injected')
        if int(done):
            self.cur.execute("select start, size from injected where pid = ?", (self.process.UniqueProcessId.v(),))
            records = self.cur.fetchall()
            if records is not None:
                scan_list.extend([(start, size, True) for start, size in records])
        else:
            scan_list.extend([(start, size, True) for start, size in self.detect_code_injections()])

        debug.debug(scan_list)
        for base_address, size_to_read, injected in scan_list:
            addr_space = self.process.get_process_address_space()
            if not addr_space:
                debug.warning("SectionList_MemorySection_PEInfo_ImportedModules_Module_ImportedFunctions_string: Cannot acquire process AS")
                return False
            data = addr_space.zread(base_address, size_to_read)
            apis = self.enum_apis(all_mods)
            calls_imported = dict(
                    (iat, call)
                    for (_, iat, call) in self.call_scan(addr_space, base_address, data)
                    if call in apis
                    )
            if injected:
                self._vicinity_scan(addr_space, calls_imported, apis, base_address, len(data), True, True)
                self._vicinity_scan(addr_space, calls_imported, apis, base_address, len(data), False, True)
            else:
                self._vicinity_scan(addr_space, calls_imported, apis, base_address, len(data), True, False)
                self._vicinity_scan(addr_space, calls_imported, apis, base_address, len(data), False, False)
            for iat, call in sorted(calls_imported.items()):
                mod_name, func_name = self._original_import(str(apis[call][0].BaseDllName or ''), apis[call][1])
                self.cur.execute("insert into impfunc values (?, ?, ?, ?, ?)", (self.process.UniqueProcessId.v(), iat, call, mod_name, func_name))
                if func_name == '':
                    continue
                if self.util.check_string(func_name, content, condition, preserve_case):
                    result = True

        self.update_done('impfunc')
        return result

    def name(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/name'.format(condition))
            return False
        return self.util.check_string(str(self.process.ImageFileName), content, condition, preserve_case)

    def ParentProcessName(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/name'.format(condition))
            return False
        if str(self.process.ImageFileName) == "System":
            return self.util.check_string('none', content, condition, preserve_case)
        self.cur.execute("select offset from hidden where pid = ?", (self.process.InheritedFromUniqueProcessId.v(),))
        res = self.cur.fetchone()
        if res is None:
            return False
        pprocess = obj.Object("_EPROCESS", offset = res[0], vm = self.flat_space)
        return self.util.check_string(str(pprocess.ImageFileName), content, condition, preserve_case)

    def cmdLine(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/cmdLine'.format(condition))
            return False
        path = self.fetchone_from_db_by_pid('hidden', 'cmdLine')
        return self.util.check_string(path, content, condition, preserve_case)

    # based on malfind
    def extract_dllpaths(self, is_path=False, is_hidden=False):
        debug.info("[time-consuming task] extracting dllpaths from VADs... (pid={0})".format(self.process.UniqueProcessId))

        inloadorder = dict((mod.DllBase.v(), mod)
                            for mod in self.process.get_load_modules())
        ininitorder = dict((mod.DllBase.v(), mod)
                            for mod in self.process.get_init_modules())
        inmemorder = dict((mod.DllBase.v(), mod)
                            for mod in self.process.get_mem_modules())

        mapped_files = {}
        for vad, address_space in self.process.get_vads(vad_filter = self.process._mapped_file_filter):
            if obj.Object("_IMAGE_DOS_HEADER", offset = vad.Start, vm = address_space).e_magic != 0x5A4D:
                continue
            mapped_files[int(vad.Start)] = str(vad.FileObject.FileName or '')

        records = []
        for base in mapped_files.keys():
            load_mod = inloadorder.get(base, None)
            init_mod = ininitorder.get(base, None)
            mem_mod = inmemorder.get(base, None)
            result = (load_mod == None) and (init_mod == None) and (mem_mod == None)
            records.append((self.process.UniqueProcessId.v(), mapped_files[base], result))

        self.cur.executemany("insert or ignore into dllpath values (?, ?, ?)", records)
        self.update_done('dllpath')
        if is_path:
            return [record[1] for record in records]
        elif is_hidden:
            return [record[2] for record in records]

    def DllPath(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/DllPath'.format(condition))
            return False

        (done,) = self.check_done('dllpath')
        if int(done):
            dllpaths = self.fetchall_from_db_by_pid('dllpath', 'path')
        else:
            dllpaths = self.extract_dllpaths(is_path=True)
        return self.util.check_strings(dllpaths, content, condition, preserve_case)

    def DllHidden(self, content, condition, preserve_case):
        if not self.util.is_condition_bool(condition):
            debug.error('{0} condition is not supported in ProcessItem/DllHidden'.format(condition))
            return False

        (done,) = self.check_done('dllpath')
        if int(done):
            hiddens = self.fetchall_from_db_by_pid('dllpath', 'hidden')
        else:
            hiddens = self.extract_dllpaths(is_hidden=True)

        if (True in hiddens and content.lower() == 'true') or (False not in hiddens and content.lower() == 'false'):
            if g_detail_on:
                sql = "select path from dllpath where pid = ? and hidden = ?"
                self.cur.execute(sql, (self.process.UniqueProcessId.v(), content.lower() == 'true'))
                records = self.cur.fetchall()
                for (path,) in records:
                    out = g_color_detail + str(path) + colorama.Fore.RESET
                    print('matched IOC term detail: {0}'.format(out))
            return True
        else:
            return False

    # based on handles
    def extract_handles(self, is_name=False, is_type=False):
        debug.info("[time-consuming task] extracting handle information... (pid={0})".format(self.process.UniqueProcessId))

        pid = self.process.UniqueProcessId
        handle_list = []
        if self.process.ObjectTable.HandleTableList:
            for handle in self.process.ObjectTable.handles():

                if not handle.is_valid():
                    continue

                name = ""
                object_type = handle.get_object_type()
                if object_type == "File":
                    file_obj = handle.dereference_as("_FILE_OBJECT")
                    name = str(file_obj.file_name_with_device())
                elif object_type == "Key":
                    key_obj = handle.dereference_as("_CM_KEY_BODY")
                    name = key_obj.full_key_name()
                elif object_type == "Process":
                    proc_obj = handle.dereference_as("_EPROCESS")
                    name = "{0}({1})".format(proc_obj.ImageFileName, proc_obj.UniqueProcessId)
                elif object_type == "Thread":
                    thrd_obj = handle.dereference_as("_ETHREAD")
                    name = "TID {0} PID {1}".format(thrd_obj.Cid.UniqueThread, thrd_obj.Cid.UniqueProcess)
                elif handle.NameInfo.Name == None:
                    name = ''
                else:
                    name = str(handle.NameInfo.Name)

                handle_list.append((int(pid), object_type, name))

            records = list(set(handle_list))
            for record in records:
                #print record
                pid, object_type, name = record
                #self.cur.execute("insert or ignore into handles values (?, ?, ?)", (pid, object_type, name.decode('utf8')))
                self.cur.execute("insert or ignore into handles values (?, ?, ?)", (pid, object_type, unicode(name))) # all executemany should be explicitly converted to unicode?
            #self.cur.executemany("insert or ignore into handles values (?, ?, ?)", records)

            self.update_done('handles')
            if is_name:
                return [record[2] for record in records]
            elif is_type:
                return [record[1] for record in records]

        self.update_done('handles')
        return None

    def HandleList_Handle_Name(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/HandleList/Handle/Name'.format(condition))
            return False

        (done,) = self.check_done('handles')
        if int(done):
            names = self.fetchall_from_db_by_pid('handles', 'name')
        else:
            names = self.extract_handles(is_name=True)
            if names is None:
                debug.warning('cannot get handles (pid = {0})'.format(self.process.UniqueProcessId))
                return False
        return self.util.check_strings(names, content, condition, preserve_case)

    def HandleList_Handle_Type(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/HandleList/Handle/Type'.format(condition))
            return False

        (done,) = self.check_done('handles')
        if int(done):
            types = self.fetchall_from_db_by_pid('handles', 'type')
        else:
            types = self.extract_handles(is_type=True)
            if types is None:
                debug.warning('cannot get handles (pid = {0})'.format(self.process.UniqueProcessId))
                return False
        return self.util.check_strings(types, content, condition, preserve_case)

    def extract_netinfo(self, is_protocol=False, is_laddr=False, is_lport=False, is_raddr=False, is_rport=False, is_state=False):
        debug.info("[time-consuming task] extracting network information...")

        net_list = []
        # addef for default values (AbstractScanCommand)
        self._config.VIRTUAL = False
        self._config.SHOW_UNALLOCATED = False
        self._config.START = None
        self._config.LENGTH = None

        for net_object, proto, laddr, lport, raddr, rport, state in netscan.Netscan.calculate(self):
            if proto.find("UDP") == -1:
                net_list.append((net_object.Owner.UniqueProcessId.v(), proto, str(laddr), int(lport), str(raddr), int(rport), str(state)))
            else:
                net_list.append((net_object.Owner.UniqueProcessId.v(), proto, str(laddr), int(lport), str(raddr), 0, str(state))) # changed rport (from "*" to 0) in UDP entry

        records = list(set(net_list))
        self.cur.executemany("insert or ignore into netinfo values (?, ?, ?, ?, ?, ?, ?)", records)

        self.update_all_done('netinfo')
        if is_protocol:
            return [record[1] for record in records if self.process.UniqueProcessId.v() == record[0]]
        elif is_laddr:
            return [record[2] for record in records if self.process.UniqueProcessId.v() == record[0]]
        elif is_lport:
            return [record[3] for record in records if self.process.UniqueProcessId.v() == record[0]]
        elif is_raddr:
            return [record[4] for record in records if self.process.UniqueProcessId.v() == record[0]]
        elif is_rport:
            return [record[5] for record in records if self.process.UniqueProcessId.v() == record[0]]
        elif is_state:
            return [record[6] for record in records if self.process.UniqueProcessId.v() == record[0]]

        return None

    def PortList_PortItem_localPort(self, content, condition, preserve_case):
        if not self.util.is_condition_integer(condition):
            debug.error('{0} condition is not supported in ProcessItem/PortList/PortItem/localPort'.format(condition))
            return False

        (done,) = self.check_done('netinfo')
        if int(done):
            lports = self.fetchall_from_db_by_pid('netinfo', 'lport')
        else:
            lports = self.extract_netinfo(is_lport=True)
            if lports is None:
                debug.warning('cannot get netinfo (pid = {0})'.format(self.process.UniqueProcessId))
                return False
        return self.util.check_integers(lports, content, condition, preserve_case)

    def PortList_PortItem_remotePort(self, content, condition, preserve_case):
        if not self.util.is_condition_integer(condition):
            debug.error('{0} condition is not supported in ProcessItem/PortList/PortItem/localPort'.format(condition))
            return False

        (done,) = self.check_done('netinfo')
        if int(done):
            rports = self.fetchall_from_db_by_pid('netinfo', 'rport')
        else:
            rports = self.extract_netinfo(is_rport=True)
            if rports is None:
                debug.warning('cannot get netinfo (pid = {0})'.format(self.process.UniqueProcessId))
                return False
        return self.util.check_integers(rports, content, condition, preserve_case)

    def PortList_PortItem_localIP(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/PortList/PortItem/localIP'.format(condition))
            return False

        (done,) = self.check_done('netinfo')
        if int(done):
            laddrs = self.fetchall_from_db_by_pid('netinfo', 'laddr')
        else:
            laddrs = self.extract_netinfo(is_laddr=True)
            if laddrs is None:
                debug.warning('cannot get netinfo (pid = {0})'.format(self.process.UniqueProcessId))
                return False
        return self.util.check_strings(laddrs, content, condition, preserve_case)

    def PortList_PortItem_remoteIP(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/PortList/PortItem/remoteIP'.format(condition))
            return False

        (done,) = self.check_done('netinfo')
        if int(done):
            raddrs = self.fetchall_from_db_by_pid('netinfo', 'raddr')
        else:
            raddrs = self.extract_netinfo(is_raddr=True)
            if raddrs is None:
                debug.warning('cannot get netinfo (pid = {0})'.format(self.process.UniqueProcessId))
                return False
        return self.util.check_strings(raddrs, content, condition, preserve_case)

    def hidden(self, content, condition, preserve_case):
        if not self.util.is_condition_bool(condition):
            debug.error('{0} condition is not supported in ProcessItem/hidden'.format(condition))
            return False

        result = self.fetchone_from_db_by_pid('hidden', 'result')
        if (result and content.lower() == 'true') or ((not result) and content.lower() == 'false'):
            return True
        else:
            return False

    # based on apihooks
    def extract_hooked_APIs(self, is_API=False, is_hookingMod=False):
        debug.info("[time-consuming task] extracting hooked APIs... (pid={0})".format(self.process.UniqueProcessId))

        process_space = self.process.get_process_address_space()
        if not process_space:
            return []

        module_group = apihooks.ModuleGroup(self.process.get_load_modules())

        records = []
        for dll in module_group.mods:
            if not process_space.is_valid_address(dll.DllBase):
                continue
            for hook in self.get_hooks(HOOK_MODE_USER, process_space, dll, module_group):
                if self.whitelist(hook.hook_mode | hook.hook_type, self.process.ImageFileName.v(), hook.VictimModule, hook.HookModule, hook.Function):
                    continue
                records.append((self.process.UniqueProcessId.v(), hook.Mode, hook.Type, str(dll.BaseDllName or ''), hook.Function, hook.HookModule))
        self.cur.executemany("insert or ignore into api_hooked values (?, ?, ?, ?, ?, ?)", records)
        self.update_done('api_hooked')
        if is_API:
            return [record[4] for record in records]
        elif is_hookingMod:
            return [record[5] for record in records]

    def Hooked_API_FunctionName(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/Hooked/API/FunctionName'.format(condition))
            return False

        (done,) = self.check_done('api_hooked')
        if int(done):
            hooked_funcs = self.fetchall_from_db_by_pid('api_hooked', 'hooked_func')
        else:
            hooked_funcs = self.extract_hooked_APIs(is_API=True)
        return self.util.check_strings(hooked_funcs, content, condition, preserve_case)

    def Hooked_API_HookingModuleName(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/Hooked/API/HookingModuleName'.format(condition))
            return False

        (done,) = self.check_done('api_hooked')
        if int(done):
            hooking_mods = self.fetchall_from_db_by_pid('api_hooked', 'hooking_module')
        else:
            hooking_mods = self.extract_hooked_APIs(is_hookingMod=True)
        return self.util.check_strings(hooking_mods, content, condition, preserve_case)

    # based on privileges
    def extract_privileges(self):
        debug.info("[time-consuming task] extracting enabled privilege information... (pid={0})".format(self.process.UniqueProcessId))
        records = []

        for value, present, enabled, default in self.process.get_token().privileges():
            try:
                name, desc = PRIVILEGE_INFO[int(value)]
            except KeyError:
                continue

            if enabled:
                records.append((self.process.UniqueProcessId.v(), name))

        self.cur.executemany("insert or ignore into privs values (?, ?)", records)
        self.update_done('privs')
        return [record[1] for record in records]

    def EnabledPrivilege_Name(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ProcessItem/EnabledPrivilege/Name'.format(condition))
            return False

        (done,) = self.check_done('privs')
        if int(done):
            privs = self.fetchall_from_db_by_pid('privs', 'priv')
        else:
            privs = self.extract_privileges()
        return self.util.check_strings(privs, content, condition, preserve_case)

class RegistryItem(hivelist.HiveList, shimcache.ShimCache):
    def __init__(self, cur, _config):
        self.cur = cur
        self._config = _config
        self.kernel_space = utils.load_as(self._config)
        self.flat_space = utils.load_as(self._config, astype = 'physical')
        self.util = ItemUtil()
        self.reg_path_list = []

    def get_path(self, keypath, key):
        if key.Name != None:
            self.reg_path_list.append('{0}'.format(keypath + "\\" + key.Name))
        for k in rawreg.subkeys(key):
            self.get_path(keypath + "\\" + key.Name, k)
        for v in rawreg.values(key):
            if key.Name != None:
                self.reg_path_list.append('{0}'.format(keypath + "\\" + key.Name + "\\" + v.Name))

    def Path(self, content, condition, preserve_case):
        debug.error('RegistryItem/Path is currently disabled because it takes toooo long time :-(')
        return False
        '''
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in RegistryItem/Path'.format(condition))
            return False

        paths = []
        count = self.util.fetchone_from_db(self.cur, "regpath", "count(*)")
        if count > 0:
            paths = self.util.fetchall_from_db(self.cur, "regpath", "path")
        else:
            debug.info("[time-consuming task] extracting registry key/value paths...")
            debug.warning('Please redefine using process handle name instead of this term because it will take too long time :-(')
            hive_offsets = []
            for hive in hivelist.HiveList.calculate(self):
                if hive.Hive.Signature == 0xbee0bee0 and hive.obj_offset not in hive_offsets:
                    hive_offsets.append(hive.obj_offset)
                    h = hivemod.HiveAddressSpace(self.kernel_space, self._config, hive.obj_offset)
                    #key = rawreg.open_key(rawreg.get_root(h), 'software\\microsoft\\windows\\currentversion\\run'.split('\\')) # <- for test
                    #if key:
                    #    self.get_path('', key)
                    self.get_path('', rawreg.get_root(h))
            paths = list(set(self.reg_path_list))
            self.cur.executemany("insert or ignore into regpath values (?)", [(path, ) for path in paths])
        return self.util.check_strings(paths, content, condition, preserve_case)
        '''

    def ShimCache_ExecutablePath(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in RegistryItem/ShimCache/ExecutablePath'.format(condition))
            return False

        paths = []
        records = []
        count = self.util.fetchone_from_db(self.cur, "shimcache", "count(*)")
        if count > 0:
            paths = self.util.fetchall_from_db(self.cur, "shimcache", "path")
        else:
            debug.info("[time-consuming task] extracting shimcache registry information...")
            for path, modified, updated in shimcache.ShimCache.calculate(self):
                path_str ='{0}'.format(path)
                records.append((path_str, modified.v()))
            if len(records) == 0:
                records.append(('dummy', 'dummy')) # insert dummy for done
            self.cur.executemany("insert or ignore into shimcache values (?, ?)", records)
            paths = [record[0] for record in records]
        return self.util.check_strings(paths, content, condition, preserve_case)

class ServiceItem(svcscan.SvcScan):
    def __init__(self, cur, _config):
        self.cur = cur
        self._config = _config
        self.kernel_space = utils.load_as(self._config)
        self.flat_space = utils.load_as(self._config, astype = 'physical')
        self.util = ItemUtil()

    def extract_services(self, is_service_name=False, is_display_name=False, is_bin_path=False):
        debug.info("[time-consuming task] extracting service information...")

        records = []
        for rec in svcscan.SvcScan.calculate(self):
            service_name = '{0}'.format(rec.ServiceName.dereference())
            display_name = '{0}'.format(rec.DisplayName.dereference())
            bin_path = '{0}'.format(rec.Binary)
            records.append((service_name, display_name, bin_path))
        self.cur.executemany("insert or ignore into service values (?, ?, ?)", records)

        if is_service_name:
            return [record[0] for record in records]
        elif is_display_name:
            return [record[1] for record in records]
        elif is_bin_path:
            return [record[2] for record in records]

    def name(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ServiceItem/name'.format(condition))
            return False

        count = self.util.fetchone_from_db(self.cur, "service", "count(*)")
        if count > 0:
            service_names = self.util.fetchall_from_db(self.cur, "service", "service_name")
        else:
            service_names = self.extract_services(is_service_name=True)
            if service_names is None:
                debug.error('cannot get service information')
        return self.util.check_strings(service_names, content, condition, preserve_case)

    def descriptiveName(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ServiceItem/descriptiveName'.format(condition))
            return False

        count = self.util.fetchone_from_db(self.cur, "service", "count(*)")
        if count > 0:
            display_names = self.util.fetchall_from_db(self.cur, "service", "display_name")
        else:
            display_names = self.extract_services(is_display_name=True)
            if display_names is None:
                debug.error('cannot get service information')
        return self.util.check_strings(display_names, content, condition, preserve_case)

    def cmdLine(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in ServiceItem/cmdLine'.format(condition))
            return False

        count = self.util.fetchone_from_db(self.cur, "service", "count(*)")
        if count > 0:
            cmdlines = self.util.fetchall_from_db(self.cur, "service", "bin_path")
        else:
            cmdlines = self.extract_services(is_bin_path=True)
            if cmdlines is None:
                debug.error('cannot get service information')
        return self.util.check_strings(cmdlines, content, condition, preserve_case)

#class DriverItem(modules.Modules, modules.UnloadedModules, modscan.ModScan, impscan.ImpScan):
class DriverItem(impscan.ImpScan, devicetree.DriverIrp, callbacks.Callbacks, timers.Timers):
    def __init__(self, kmod, cur, _config):
        self.kmod = kmod
        self.cur = cur
        self._config = _config
        self.kernel_space = utils.load_as(self._config)
        self.flat_space = utils.load_as(self._config, astype = 'physical')
        self.util = ItemUtil()
        self.forwarded_imports = { # copied from impscan
            "RtlGetLastWin32Error" : "kernel32.dll!GetLastError",
            "RtlSetLastWin32Error" : "kernel32.dll!SetLastError",
            "RtlRestoreLastWin32Error" : "kernel32.dll!SetLastError",
            "RtlAllocateHeap" : "kernel32.dll!HeapAlloc",
            "RtlReAllocateHeap" : "kernel32.dll!HeapReAlloc",
            "RtlFreeHeap" : "kernel32.dll!HeapFree",
            "RtlEnterCriticalSection" : "kernel32.dll!EnterCriticalSection",
            "RtlLeaveCriticalSection" : "kernel32.dll!LeaveCriticalSection",
            "RtlDeleteCriticalSection" : "kernel32.dll!DeleteCriticalSection",
            "RtlZeroMemory" : "kernel32.dll!ZeroMemory",
            "RtlSizeHeap" : "kernel32.dll!HeapSize",
            "RtlUnwind" : "kernel32.dll!RtlUnwind",
            }

    def fetchall_from_db_by_base(self, table, column):
        name = str(self.kmod.BaseDllName  or '')
        base_addr = self.kmod.DllBase
        debug.debug("fetchall: {0} already done. Results reused (name={1}, base=0x{2:x})".format(table, name, base_addr))
        sql = "select {0} from {1} where base = ?".format(column, table)
        #self.cur.execute(sql, (base_addr.v(),))
        self.cur.execute(sql, (str(base_addr.v()),)) # for unsigned long ("OverflowError: Python int too large to convert to SQLite INTEGER")
        return [record[0] for record in self.cur.fetchall()]

    def fetchone_from_db_by_base(self, table, column):
        name = str(self.kmod.BaseDllName  or '')
        base_addr = self.kmod.DllBase
        debug.debug("fetchone: {0} from {1} (name={2}, base=0x{3:x})".format(column, table, name, base_addr))
        sql = "select {0} from {1} where base = ?".format(column, table)
        #self.cur.execute(sql, (base_addr.v(),))
        self.cur.execute(sql, (str(base_addr.v()),))
        return self.cur.fetchone()[0]

    def DriverName(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in DriverItem/DriverName'.format(condition))
            return False
        return self.util.check_string(str(self.kmod.BaseDllName  or ''), content, condition, preserve_case)

    def get_data(self):
        base_address = self.kmod.DllBase
        size_to_read = self.kmod.SizeOfImage
        data = ""
        mod_filepath = os.path.join(g_cache_path, 'kmod_0x{0:x}'.format(self.kmod.DllBase)) + '.sys'

        if os.path.exists(mod_filepath):
            with open(mod_filepath, 'rb') as f:
                data = f.read()
        else:
            if not size_to_read:
                pefile = obj.Object("_IMAGE_DOS_HEADER",
                                    offset = base_address,
                                    vm = self.kernel_space)
                try:
                    nt_header = pefile.get_nt_header()
                    size_to_read = nt_header.OptionalHeader.SizeOfImage
                except ValueError:
                    pass
                if not size_to_read:
                    debug.warning('cannot get size info (kernel module name={0} base=0x{1:x})'.format(str(self.kmod.BaseDllName  or ''), self.kmod.DllBase))

            procs = list(tasks.pslist(self.kernel_space))
            kernel_space = tasks.find_space(self.kernel_space, procs, base_address) # for some GUI drivers (e.g., win32k.sys)
            if not kernel_space:
                debug.warning('Cannot read supplied address (kernel module name={0} base=0x{1:x})'.format(str(self.kmod.BaseDllName  or ''), self.kmod.DllBase))
            else:
                data = kernel_space.zread(base_address, size_to_read)
            with open(mod_filepath, 'wb') as f:
                f.write(data)

        return base_address, size_to_read, data

    # based on impscan
    def PEInfo_ImportedModules_Module_ImportedFunctions_string(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in DriverItem/PEInfo/ImportedModules/Module/ImportedFunctions/string'.format(condition))
            return False

        imp_funcs = []
        count = self.fetchone_from_db_by_base("kernel_mods_impfunc", "count(*)")
        if count > 0:
            imp_funcs = self.fetchall_from_db_by_base("kernel_mods_impfunc", "func_name")
        else:
            debug.info("[time-consuming task] extracting import functions... (kernel module name={0} base=0x{1:x})".format(str(self.kmod.BaseDllName  or ''), self.kmod.DllBase))
            records = []

            all_mods = list(win32.modules.lsmod(self.kernel_space))
            base_address, size_to_read, data = self.get_data()

            if data != '':
                apis = self.enum_apis(all_mods)
                procs = list(tasks.pslist(self.kernel_space))
                addr_space = tasks.find_space(self.kernel_space, procs, base_address) # for some GUI drivers (e.g., win32k.sys)

                calls_imported = dict(
                        (iat, call)
                        for (_, iat, call) in self.call_scan(addr_space, base_address, data)
                        if call in apis
                        )
                self._vicinity_scan(addr_space,
                        calls_imported, apis, base_address, len(data),
                        forward = True)
                self._vicinity_scan(addr_space,
                        calls_imported, apis, base_address, len(data),
                        forward = False)

                for iat, call in sorted(calls_imported.items()):
                    mod_name, func_name = self._original_import(str(apis[call][0].BaseDllName or ''), apis[call][1])
                    #records.append((self.kmod.DllBase.v(), iat, call, mod_name, func_name))
                    records.append((str(self.kmod.DllBase.v()), str(iat), str(call), mod_name, func_name))
                    imp_funcs.append(func_name)

            if len(records) == 0:
                debug.info('inserting marker "done"... (kernel module name={0} base=0x{1:x})'.format(str(self.kmod.BaseDllName  or ''), self.kmod.DllBase))
                records.append((str(self.kmod.DllBase.v()), 0, 0, 'marker_done', 'marker_done'))
            self.cur.executemany("insert or ignore into kernel_mods_impfunc values (?, ?, ?, ?, ?)", records)

        return self.util.check_strings(imp_funcs, content, condition, preserve_case)

    def StringList_string(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in DriverItem/StringList/string'.format(condition))
            return False

        count = self.fetchone_from_db_by_base("kernel_mods_strings", "count(*)")
        strings = []
        records = []
        if count > 0:
            strings = self.fetchall_from_db_by_base("kernel_mods_strings", "string")
        else:
            debug.info("[time-consuming task] extracting strings... (kernel module name={0} base=0x{1:x})".format(str(self.kmod.BaseDllName  or ''), self.kmod.DllBase))
            base_address, size_to_read, data = self.get_data()
            if data != '':
                strings = list(set(self.util.extract_unicode(data) + self.util.extract_ascii(data)))
                records = [(str(self.kmod.DllBase.v()), string) for string in strings]
            if len(records) == 0:
                debug.info('inserting marker "done"... (kernel module name={0} base=0x{1:x})'.format(str(self.kmod.BaseDllName  or ''), self.kmod.DllBase))
                records.append((str(self.kmod.DllBase.v()), 'marker_done'))
            self.cur.executemany("insert or ignore into kernel_mods_strings values (?, ?)", records)

        result = self.util.check_strings(strings, content, condition, preserve_case)
        if result == False and condition == 'matches': # for searching binary sequences
            pattern = self.util.make_regex(content, preserve_case)
            base_address, size_to_read, data = self.get_data()
            if data != '' and pattern.search(data) is not None:
                result = True

        return result

    # based on devicetree.py
    def extract_IRP_info(self):
        debug.info("[time-consuming task] extracting hooking module names in IRP array...")

        records = []
        # added for default option values (AbstractScanCommand)
        self._config.VIRTUAL = False
        self._config.SHOW_UNALLOCATED = False
        self._config.START = None
        self._config.LENGTH = None

        mods = dict((self.kernel_space.address_mask(mod.DllBase), mod) for mod in win32.modules.lsmod(self.kernel_space))
        mod_addrs = sorted(mods.keys())

        for driver in devicetree.DriverIrp.calculate(self):
            header = driver.get_object_header()
            driver_name = str(header.NameInfo.Name or '')

            for i, function in enumerate(driver.MajorFunction):
                function = driver.MajorFunction[i]
                module = tasks.find_module(mods, mod_addrs, self.kernel_space.address_mask(function))
                if module:
                    module_name = str(module.BaseDllName or '')
                else:
                    module_name = "Unknown"
                #records.append((driver.DriverStart.v(), MAJOR_FUNCTIONS[i], function.v(), module_name))
                records.append((str(driver.DriverStart.v()), str(MAJOR_FUNCTIONS[i]), str(function.v()), module_name))

        self.cur.executemany("insert or ignore into kernel_mods_irp values (?, ?, ?, ?)", records)
        return [record[3] for record in records if self.kmod.DllBase.v() == record[0]]

    def IRP_HookingModuleName(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in DriverItem/IRP/HookingModuleName'.format(condition))
            return False

        mod_names = []
        count = self.util.fetchone_from_db(self.cur, "kernel_mods_irp", "count(*)")
        if count > 0:
            mod_names = self.fetchall_from_db_by_base("kernel_mods_irp", "mod_name")
        else:
            mod_names = self.extract_IRP_info()
        return self.util.check_strings(mod_names, content, condition, preserve_case)

    # based on callbacks
    def extract_callbacks(self):
        debug.info("[time-consuming task] extracting kernel callbacks...")

        records = []
        # added for default option values (filescan)
        self._config.VIRTUAL = False
        self._config.SHOW_UNALLOCATED = False
        self._config.START = None
        self._config.LENGTH = None

        for (sym, cb, detail), mods, mod_addrs in callbacks.Callbacks.calculate(self):
            module = tasks.find_module(mods, mod_addrs, mods.values()[0].obj_vm.address_mask(cb))
            type_name = '{0}'.format(sym)
            #records.append((module.DllBase.v(), type_name, cb.v(), str(detail or "-")))
            records.append((str(module.DllBase.v()), type_name, str(cb.v()), str(detail or "-")))

        if len(records) == 0:
            records.append(('dummy', 'dummy', 'dummy', 'dummy')) # insert dummy for done
        self.cur.executemany("insert or ignore into kernel_mods_callbacks values (?, ?, ?, ?)", records)
        return [record[1] for record in records if self.kmod.DllBase.v() == record[0]]

    def CallbackRoutine_Type(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in DriverItem/CallbackRoutine/Type'.format(condition))
            return False

        types = []
        count = self.util.fetchone_from_db(self.cur, "kernel_mods_callbacks", "count(*)")
        if count > 0:
            types = self.fetchall_from_db_by_base("kernel_mods_callbacks", "type")
        else:
            types = self.extract_callbacks()
        return self.util.check_strings(types, content, condition, preserve_case)

    # based on timers
    def extract_timers(self):
        debug.info("[time-consuming task] extracting kernel timers...")

        records = []
        # added for default option value
        self._config.LISTHEAD = None

        for timer, module in timers.Timers.calculate(self):
            if timer.Header.SignalState.v():
                signaled = "Yes"
            else:
                signaled = "-"
            due_time = "{0:#010x}:{1:#010x}".format(timer.DueTime.HighPart, timer.DueTime.LowPart)
            #records.append((module.DllBase.v(), timer.obj_offset, due_time, timer.Period.v(), signaled, timer.Dpc.DeferredRoutine.v()))
#Quickfix for module=None
	    if module is None:
            	records.append(('None', str(timer.obj_offset), due_time, timer.Period.v(), signaled, str(timer.Dpc.DeferredRoutine.v())))
            else:
            	records.append((str(module.DllBase.v()), str(timer.obj_offset), due_time, timer.Period.v(), signaled, str(timer.Dpc.DeferredRoutine.v())))

        if len(records) == 0:
            records.append(('dummy', 'dummy', 'dummy', 'dummy', 'dummy', 'dummy')) # insert dummy for done
        self.cur.executemany("insert or ignore into kernel_mods_timers values (?, ?, ?, ?, ?, ?)", records)
        timer_routines = [record[5] for record in records if self.kmod.DllBase.v() == record[0]]
        return len(timer_routines)

    def TimerRoutineIncluded(self, content, condition, preserve_case):
        if not self.util.is_condition_bool(condition):
            debug.error('{0} condition is not supported in DriverItem/TimerRoutineIncluded'.format(condition))
            return False

        included = 0
        count = self.util.fetchone_from_db(self.cur, "kernel_mods_timers", "count(*)") # total
        if count > 0:
            included = self.fetchall_from_db_by_base("kernel_mods_timers", "count(*)")[0] # per kmod
        else:
            included = self.extract_timers()

        debug.debug('{0}={1}'.format(str(self.kmod.BaseDllName  or ''), included))
        if (included > 0 and content.lower() == 'true') or (included == 0 and content.lower() == 'false'):
            return True
        else:
            return False

class HookItem(ssdt.SSDT):
    def __init__(self, cur, _config):
        self.cur = cur
        self._config = _config
        self.kernel_space = utils.load_as(self._config)
        self.flat_space = utils.load_as(self._config, astype = 'physical')
        self.util = ItemUtil()

    # based on ssdt
    def extract_SSDT_hooked_functions(self):
        debug.info("[time-consuming task] extracting hooked entries in SSDT...")
        records = []

        # need to be modified in the future
        hooked_wl = ['win32k.sys', 'ntkrnlpa.exe', 'ntoskrnl.exe', 'ntkrnlmp.exe', 'ntkrpamp.exe']
        hooked_wl_inline = ['win32k.sys', 'ntkrnlpa.exe', 'ntoskrnl.exe', 'ntkrnlmp.exe', 'ntkrpamp.exe', 'hal.dll']

        addr_space = utils.load_as(self._config)
        syscalls = addr_space.profile.syscalls
        bits32 = addr_space.profile.metadata.get('memory_model', '32bit') == '32bit'

        for idx, table, n, vm, mods, mod_addrs in ssdt.SSDT.calculate(self):
            for i in range(n):
                if bits32:
                    syscall_addr = obj.Object('address', table + (i * 4), vm).v()
                else:
                    offset = obj.Object('long', table + (i * 4), vm).v()
                    syscall_addr = table + (offset >> 4)
                try:
                    syscall_name = syscalls[idx][i]
                except IndexError:
                    syscall_name = "UNKNOWN"

                syscall_mod = tasks.find_module(mods, mod_addrs, addr_space.address_mask(syscall_addr))
                if syscall_mod:
                    syscall_modname = syscall_mod.BaseDllName.v()
                else:
                    syscall_modname = "UNKNOWN"

                if syscall_modname.lower() not in hooked_wl:
                    records.append((idx, idx * 0x1000 + i, syscall_addr, syscall_name, syscall_modname, False))
                    continue

                hook_name = ''
                # for inline hook
                if (addr_space.profile.metadata.get('memory_model', '32bit') == '32bit' and syscall_mod is not None):
                    ret = apihooks.ApiHooks.check_inline(va = syscall_addr, addr_space = vm,
                                            mem_start = syscall_mod.DllBase,
                                            mem_end = syscall_mod.DllBase + syscall_mod.SizeOfImage)
                    if ret is not None:
                        (hooked, data, dest_addr) = ret
                        if hooked:
                            hook_mod = tasks.find_module(mods, mod_addrs, dest_addr)
                            if hook_mod:
                                hook_name = hook_mod.BaseDllName.v()
                            else:
                                hook_name = "UNKNOWN"

                            if hook_name.lower() not in hooked_wl_inline:
                                records.append((idx, idx * 0x1000 + i, dest_addr, syscall_name, hook_name, True))

        if len(records) == 0:
            records.append(('dummy', 'dummy', 'dummy', 'dummy', 'dummy', 'dummy')) # insert dummy for done
        self.cur.executemany("insert or ignore into ssdt_hooked values (?, ?, ?, ?, ?, ?)", records)
        return [record[3] for record in records]

    def SSDT_HookedFunctionName(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in HookItem/SSDT/HookedFunctionName'.format(condition))
            return False

        syscall_names = []
        count = self.util.fetchone_from_db(self.cur, "ssdt_hooked", "count(*)")
        if count > 0:
            syscall_names = self.util.fetchall_from_db(self.cur, "ssdt_hooked", "syscall_name")
        else:
            syscall_names = self.extract_SSDT_hooked_functions()
        return self.util.check_strings(syscall_names, content, condition, preserve_case)

class FileItem(mftparser.MFTParser):
    def __init__(self, cur, _config):
        self.cur = cur
        self._config = _config
        self.kernel_space = utils.load_as(self._config)
        self.flat_space = utils.load_as(self._config, astype = 'physical')
        self.util = ItemUtil()

    # based on mftparser
    def extract_MFT_entries(self, is_inode=False, is_name=False, is_extension=False, is_path=False, is_size=False):
        debug.info("[time-consuming task] extracting NTFS MFT entries...")
        records = []

        # added for default option values (mftparser)
        self._config.MACHINE = ""
        self._config.OFFSET = None
        self._config.ENTRYSIZE = 1024
        self._config.DEBUGOUT = False
        self._config.NOCHECK = False

        for offset, mft_entry, attributes in mftparser.MFTParser.calculate(self):
            full = ""
            for a, i in attributes:
                size = -1
                if a.startswith("FILE_NAME"):
		    debug.debug(a)
                    if hasattr(i, "ParentDirectory"):
                        name = mft_entry.remove_unprintable(i.get_name()) or "(Null)"
                        if len(name.split('.')) > 1:
                            ext = name.split('.')[-1]
                        else:
                            ext = ''
                        full = mft_entry.get_full_path(i)
                        #size = int(i.RealFileSize)
                        size = str(i.RealFileSize.v())
                        debug.debug('NTFS file info from MFT entry $FN: name={0}, ext={1}, full={2}'.format(name, ext, full))
                        #records.append((offset, mft_entry.RecordNumber.v(), name, ext, full, size))
                        records.append((offset, mft_entry.RecordNumber.v(), unicode(name), unicode(ext), unicode(full), unicode(size)))

        if len(records) == 0:
            records.append((0, 0, 'dummy', 'dummy', 'dummy', 0)) # insert dummy for done
        self.cur.executemany("insert or ignore into files values (?, ?, ?, ?, ?, ?)", records)

        if is_inode:
            return [record[1] for record in records]
        elif is_name:
            return [record[2] for record in records]
        elif is_extension:
            return [record[3] for record in records]
        elif is_path:
            return [record[4] for record in records]
        elif is_size:
            #return [record[5] for record in records]
            return [long(record[5]) for record in records]

    def INode(self, content, condition, preserve_case):
        if not self.util.is_condition_integer(condition):
            debug.error('{0} condition is not supported in FileItem/INode'.format(condition))
            return False

        ent_nums = []
        count = self.util.fetchone_from_db(self.cur, "files", "count(*)")
        if count > 0:
            ent_nums = self.util.fetchall_from_db(self.cur, "files", "inode")
        else:
            ent_nums = self.extract_MFT_entries(is_inode=True)
        return self.util.check_integers(ent_nums, content, condition, preserve_case)

    def FileName(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in FileItem/FileName'.format(condition))
            return False

        names = []
        count = self.util.fetchone_from_db(self.cur, "files", "count(*)")
        if count > 0:
            names = self.util.fetchall_from_db(self.cur, "files", "name")
        else:
            names = self.extract_MFT_entries(is_name=True)
        return self.util.check_strings(names, content, condition, preserve_case)

    def FileExtension(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in FileItem/FileExtension'.format(condition))
            return False

        exts = []
        count = self.util.fetchone_from_db(self.cur, "files", "count(*)")
        if count > 0:
            exts = self.util.fetchall_from_db(self.cur, "files", "extension")
        else:
            exts = self.extract_MFT_entries(is_extension=True)
        return self.util.check_strings(exts, content, condition, preserve_case)

    def FullPath(self, content, condition, preserve_case):
        if not self.util.is_condition_string(condition):
            debug.error('{0} condition is not supported in FileItem/FullPath'.format(condition))
            return False

        paths = []
        count = self.util.fetchone_from_db(self.cur, "files", "count(*)")
        if count > 0:
            paths = self.util.fetchall_from_db(self.cur, "files", "path")
        else:
            paths = self.extract_MFT_entries(is_path=True)
        return self.util.check_strings(paths, content, condition, preserve_case)

    def SizeInBytes(self, content, condition, preserve_case):
        if not self.util.is_condition_integer(condition):
            debug.error('{0} condition is not supported in FileItem/SizeInBytes'.format(condition))
            return False

        sizes = []
        count = self.util.fetchone_from_db(self.cur, "files", "count(*)")
        if count > 0:
            sizes = self.util.fetchall_from_db(self.cur, "files", "size")
        else:
            sizes = self.extract_MFT_entries(is_size=True)
        return self.util.check_integers(sizes, content, condition, preserve_case)

class IOCParseError(Exception):
    pass

class IOC_Scanner:
    def __init__(self):
        self.iocs = {} # elementTree representing the IOC
        self.ioc_name = {} # guid -> name mapping
        self.level = 1 # xml hierarchical level in the IOC
        self.iocEvalString = '' # AND/OR logic of the IOC evaluation
        self.iocLogicString = '' # AND/OR logic result for display
        self.item_obj = None
        self.display_mode = False
        self.cur = None
        self._config = None
        self.items = {'Process':None, 'Registry':None, 'Service':None, 'Driver':None, 'Hook':None, 'File':None}
        self.checked_results = {} # for repeatedly checked Items except ProcessItem and DriverItem
        self.total_score = 0

    def __len__(self):
        return len(self.iocs)

    def insert(self, filename):
        errors = []
        if os.path.isfile(filename):
            debug.info('loading IOC from: {0}'.format(filename))
            try:
                self.parse(ioc_api.IOC(filename))
            except ioc_api.IOCParseError,e:
                debug.error('Parse Error [{0}]'.format(e))
        elif os.path.isdir(filename):
            debug.info('loading IOCs from: {0}'.format(filename))
            for fn in glob.glob(filename+os.path.sep+'*.ioc'):
                if not os.path.isfile(fn):
                    continue
                else:
                    try:
                        self.parse(ioc_api.IOC(fn))
                    except ioc_api.IOCParseError,e:
                        debug.error('Parse Error [{0}]'.format(str(e)))
        else:
            pass
        debug.info('Parsed [{0}] IOCs'.format(str(len(self))))
        return errors

    def parse(self, ioc_obj):
        if ioc_obj is None:
            return
        iocid = ioc_obj.root.get('id')
        if iocid in self.iocs:
            debug.error('duplicate IOCs (UUID={0})'.format(iocid))

        # check items
        try:
            ioc_logic = ioc_obj.root.xpath('.//criteria')[0]
        except IndexError, e:
            debug.warning('Could not find criteria nodes for IOC [{0}]. '.format(str(iocid)))
            return
        for document in ioc_logic.xpath('//Context/@document'):
            item_name = document[:-4]
            if not item_name in self.items.keys():
                debug.error('Not supported item = {0} in IOC [{1}]. '.format(document, str(iocid)))
                return

        self.iocs[iocid] = ioc_obj
        return True

    def prepare(self, cur, _config):
        self.cur = cur
        self._config = _config

    def with_item(self, iocid, name):
        ioc_obj = self.iocs[iocid]
        ioc_logic = ioc_obj.root.xpath('.//criteria')[0]
        if len(ioc_logic.xpath('//Context[@document="{0}Item"]'.format(name))) > 0:
            return True
        else:
            return False

    def with_item_all(self, name):
        for iocid in self.iocs:
            if self.with_item(iocid, name):
                return True
        return False

    def check_indicator_item(self, node, params, is_last_item):
        iocResult = False
        global g_detail_on
        score = 0

        condition = node.get('condition')
        preserve_case = node.get('preserve-case')
        negate = node.get('negate')

        document = node.xpath('Context/@document')[0]
        search = node.xpath('Context/@search')[0]
        content = node.findtext('Content')
        logicOperator = str(node.getparent().get("operator")).lower()

        theid = node.get('id')
        param_desc = ''
        param_cnt = 0
        note = ''
        for refid, name, value in params:
            if theid == refid:
                if name == 'detail' and value == 'on':
                    g_detail_on = True
                    param_cnt += 1
                elif name == 'score':
                    score += int(value)
                    param_cnt += 1
                elif name == 'note':
                    param_cnt += 1
                    note = value
        if param_cnt > 0:
            param_desc = ' ('
            if g_detail_on:
                param_desc += 'detail=on;'
            if score > 0:
                param_desc += 'score={0};'.format(score)
            if note != '':
                param_desc += 'note="{0}";'.format(note)
            param_desc += ')'

        if negate == 'true':
            item_desc = 'Not ' + search + ' ' + condition + ' ' + content + param_desc
        else:
            item_desc = search + ' ' + condition + ' ' + content + param_desc

        if self.display_mode:
            if is_last_item:
                self.iocLogicString += '  '*self.level + item_desc + '\n'
            else:
                self.iocLogicString += '  '*self.level + item_desc + '\n' + '  '*self.level + str(logicOperator) + '\n'
            return

        method = '_'.join(search.split('/')[1:])
        item_name = document[:-4] # fetch '*' from '*Item'
        if not item_name in self.items.keys():
            debug.error('{0} not supported in this plugin'.format(document))
        if item_name != 'Process' and item_name != 'Driver' and self.items[item_name] is None:
            self.items[item_name] = eval('{0}(self.cur, self._config)'.format(document))
        if not method in dir(self.items[item_name]):
            debug.error('{0} not supported in this plugin'.format(search))

        the_term = search + content + condition + preserve_case
        if item_name != 'Process' and item_name != 'Driver' and (the_term) in self.checked_results.keys():
            debug.debug('reusing results about other Items except repeated ProcessItem/DriverItem ("{0}" = {1})'.format(the_term, self.checked_results[the_term]))
            iocResult = self.checked_results[the_term]
        else:
            iocResult = eval('self.items["{0}"].{1}(r"{2}","{3}","{4}")'.format(item_name, method, content, condition, preserve_case))
            #if negate == 'true' and iocResult == True:
            if negate == 'true':
                iocResult = not iocResult
        if item_name != 'Process' and item_name != 'Driver' and (the_term) not in self.checked_results.keys():
            self.checked_results[the_term] = iocResult

        if is_last_item:
            self.iocEvalString += ' ' + str(iocResult)
            if iocResult:
                self.iocLogicString += '  '*self.level + colorama.Style.BRIGHT + g_color_term + '>>> ' + item_desc + colorama.Fore.RESET + colorama.Style.RESET_ALL + '\n'
                self.total_score += score
            else:
                self.iocLogicString += '  '*self.level + item_desc + '\n'
        else:
            self.iocEvalString += ' ' + str(iocResult) + ' ' + str(logicOperator)
            if iocResult:
                self.iocLogicString += '  '*self.level + colorama.Style.BRIGHT + g_color_term + '>>> '  + item_desc + colorama.Fore.RESET + colorama.Style.RESET_ALL + '\n' + '  '*self.level + str(logicOperator) + '\n'
                self.total_score += score
            else:
                self.iocLogicString += '  '*self.level + item_desc + '\n' + '  '*self.level + str(logicOperator) + '\n'

        g_detail_on = False

    def walk_indicator(self, node, params):
        expected_tag = 'Indicator'
        if node.tag != expected_tag:
            raise ValueError('node expected tag is [{0}]'.format(expected_tag))

        debug.debug('entering walk_indicator: {0}={1}'.format(node.get('id'), node.get('operator')))
        for chn in node.getchildren():
            chn_id = chn.get('id')

            if chn.tag == 'IndicatorItem':
                if chn == node.getchildren()[-1]:
                    self.check_indicator_item(chn, params, True)
                else:
                    self.check_indicator_item(chn, params, False)

            elif chn.tag == 'Indicator':
                debug.debug('parent id=operator: {0}={1}'.format(chn.getparent().get('id'), chn.getparent().get('operator')))
                operator = chn.get('operator').lower()
                if operator not in ['or', 'and']:
                    raise IOCParseError('Indicator@operator is not AND/OR. [{0}] has [{1}]'.format(chn_id, operator) )

                self.iocEvalString += ' ('
                self.iocLogicString += '  '*self.level + '(\n'
                self.level+=1

                self.walk_indicator(chn, params)

                self.level-=1
                logicOperator = str(chn.getparent().get("operator")).lower()
                if chn == node.getchildren()[-1]:
                    self.iocLogicString += '  '*self.level + ')\n'
                    self.iocEvalString += ' )'
                else:
                    '''
                    theid = chn.getparent().get('id')
                    print theid
                    for refid, name, value in params:
                        if theid == refid:
                            if name == 'note':
                                logicOperator += '(note="{0}")'.format(value)
                    '''
                    self.iocLogicString += '  '*self.level + ')\n' + '  '*self.level + str(logicOperator) + '\n'
                    self.iocEvalString += ' )' + ' ' + str(logicOperator)

            else:
                # should never get here
                raise IOCParseError('node is not a Indicator/IndicatorItem')

    def walk_parameter(self, node):
        expected_tag = 'parameters'
        if node.tag != expected_tag:
            raise ValueError('walk_parameter: node expected tag is [{0}]'.format(expected_tag))

        params = []
        for chn in node.getchildren():
            if chn.tag != 'param':
                raise ValueError('walk_parameter: chn expected tag is [param]')
            #theid = chn.get('id')
            refid = chn.get('ref-id')
            name = chn.get('name')
            value = chn.findtext('value')
            params.append((refid, name, value))

        return params

    def scan(self, iocid, process, kmod):
        result = ''

        if len(self) < 1:
            debug.error('no iocs available to scan')
            return result

        if process is not None:
            self.items['Process'] = ProcessItem(process, self.cur, self._config)
        if kmod is not None:
            self.items['Driver'] = DriverItem(kmod, self.cur, self._config)

        ioc_obj = self.iocs[iocid]

        try:
            ioc_params = ioc_obj.root.xpath('.//parameters')[0]
            #params = ioc_params.getchildren()[0]
        except IndexError, e:
            debug.debug('Could not find children for the top level parameters/children nodes for IOC [{0}]'.format(str(iocid)))
        else:
            params = self.walk_parameter(ioc_params)

        ioc_logic = ioc_obj.root.xpath('.//criteria')[0]
        try:
            tlo = ioc_logic.getchildren()[0]
        except IndexError, e:
            debug.warning('Could not find children for the top level criteria/children nodes for IOC [{0}]'.format(str(iocid)))
            return result

        self.walk_indicator(tlo, params)
        debug.debug(self.iocEvalString)
        if eval(self.iocEvalString):
            result += 'IOC matched (by logic)! short_desc="{0}" id={1}\n'.format(ioc_obj.metadata.findtext('.//short_description'), iocid)
            result += 'logic (matched item is magenta-colored):\n{0}'.format(self.iocLogicString)
        elif self.total_score >= SCORE_THRESHOLD:
            result += 'IOC matched (by score)! short_desc="{0}" id={1}\n'.format(ioc_obj.metadata.findtext('.//short_description'), iocid)
            result += 'logic (matched item is magenta-colored):\n{0}'.format(self.iocLogicString)
        elif self._config.test:
            result += '[Test Mode for improving IOC] short_desc="{0}" id={1}\n'.format(ioc_obj.metadata.findtext('.//short_description'), iocid)
            result += 'logic (matched item is magenta-colored):\n{0}'.format(self.iocLogicString)
        self.iocEvalString=""
        self.iocLogicString=""
        self.total_score = 0

        self.items['Process'] = None
        self.items['Driver'] = None
        return result

    def display(self):
        self.display_mode = True
        result = ''

        if len(self) < 1:
            debug.error('no iocs to display')
            return result

        for iocid in self.iocs:
            ioc_obj = self.iocs[iocid]

            try:
                ioc_params = ioc_obj.root.xpath('.//parameters')[0]
                #params = ioc_params.getchildren()[0]
            except IndexError, e:
                debug.debug('Could not find children for the top level parameters/children nodes for IOC [{0}]'.format(str(iocid)))
            else:
                params = self.walk_parameter(ioc_params)

            ioc_logic = ioc_obj.root.xpath('.//criteria')[0]
            try:
                tlo = ioc_logic.getchildren()[0]
            except IndexError, e:
                debug.warning('Could not find children for the top level criteria/children nodes for IOC [{0}]'.format(str(iocid)))
                continue

            self.walk_indicator(tlo, params)
            result += '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n'
            result += 'IOC definition \nshort_desc: {0} \ndesc: {1} \nid: {2}\n'.format(ioc_obj.metadata.findtext('.//short_description'), ioc_obj.metadata.findtext('.//description'), iocid)
            result += 'logic:\n{0}'.format(self.iocLogicString)
            self.iocLogicString=""

        return result

class OpenIOC_Scan(psxview.PsXview, taskmods.DllList):
    """Scan OpenIOC 1.1 based indicators"""
    meta_info = commands.Command.meta_info
    meta_info['author'] = 'Takahiro Haruyama'
    meta_info['copyright'] = 'Copyright (c) 2014 Takahiro Haruyama'
    meta_info['license'] = 'GNU General Public License 2.0'
    meta_info['url'] = 'http://takahiroharuyama.github.io/'

    def __init__(self, config, *args, **kwargs):
        common.AbstractWindowsCommand.__init__(self, config, *args, **kwargs)
        self._config.add_option('PID', short_option = 'p', default = None,
                                help = 'Operate on these Process IDs (comma-separated)',
                                action = 'store', type = 'str')
        self._config.add_option('ioc_dir', short_option = 'i', default = None,
                               help = 'Location of IOCs directory',
                               action = 'store', type = 'str')
        self._config.add_option('show', short_option = 's', default = False,
                               help = 'Display IOC definition only',
                               action = 'store_true')
        self._config.add_option('cache_path', short_option = 'c', default = None,
                               help = 'Specify the cache folder path of analysis result',
                               action = 'store', type = 'str')
        self._config.add_option('kmod', short_option = 'm', default = None,
                               help = 'Operate on these kernel module names (comma-separated, case-insensitive)',
                               action = 'store', type = 'str')
        self._config.add_option('test', short_option = 't', default = False,
                               help = 'display all scan results for improving IOC',
                               action = 'store_true')
        self._config.add_option('erase', short_option = 'e', default = False,
                               help = 'erase cached db then scan',
                               action = 'store_true')
        self._config.add_option('not_carve', short_option = 'n', default = False,
                               help = 'not carve _EPROCESS object (fetch from linked list)',
                               action = 'store_true')
        self.db = None
        self.cur = None
        self.total_secs = 0

    def filter_tasks(self, tasks):
        if self._config.PID is None:
            return tasks

        try:
            pidlist = [int(p) for p in self._config.PID.split(',')]
        except ValueError:
            debug.error("Invalid PID {0}".format(self._config.PID))

        return [t for t in tasks if t.UniqueProcessId in pidlist]

    def clear_tables(self):
        debug.info("Clearing the tables... (old DB or erase flag)")

        self.cur.execute("drop table if exists version")

        self.cur.execute("drop table if exists hidden")
        self.cur.execute("drop table if exists done")
        self.cur.execute("drop table if exists injected")
        self.cur.execute("drop table if exists strings")
        self.cur.execute("drop table if exists vaddump")
        self.cur.execute("drop table if exists impfunc")
        self.cur.execute("drop table if exists handles")
        self.cur.execute("drop table if exists netinfo")
        self.cur.execute("drop table if exists dllpath")
        self.cur.execute("drop table if exists api_hooked")
        self.cur.execute("drop table if exists privs")

        self.cur.execute("drop table if exists kernel_mods")
        self.cur.execute("drop table if exists kernel_mods_impfunc")
        self.cur.execute("drop table if exists kernel_mods_strings")
        self.cur.execute("drop table if exists kernel_mods_irp")
        self.cur.execute("drop table if exists kernel_mods_callbacks")
        self.cur.execute("drop table if exists kernel_mods_timers")

        self.cur.execute("drop table if exists regpath")
        self.cur.execute("drop table if exists shimcache")
        self.cur.execute("drop table if exists service")
        self.cur.execute("drop table if exists ssdt_hooked")
        self.cur.execute("drop table if exists files")

    def make_tables(self):
        debug.info("Making new DB tables...")

        self.cur.execute("create table if not exists version(version unique)")
        self.cur.execute("insert into version values(?)", (g_version,))

        self.cur.execute("create table if not exists hidden(pid unique, result, offset unique, cmdline)")
        self.cur.execute("create table if not exists done(pid unique, injected, strings, vaddump, impfunc, handles, netinfo, dllpath, api_hooked, privs)")
        self.cur.execute("create table if not exists injected(pid, start, size)")
        self.cur.execute("create table if not exists strings(pid, string)")
        self.cur.execute("create table if not exists vaddump(pid unique, size)")
        self.cur.execute("create table if not exists impfunc(pid, iat, call, mod_name, func_name)")
        self.cur.execute("create table if not exists handles(pid, type, name)")
        self.cur.execute("create table if not exists netinfo(pid, protocol, laddr, lport, raddr, rport, state)")
        self.cur.execute("create table if not exists dllpath(pid, path, hidden)")
        self.cur.execute("create table if not exists api_hooked(pid, mode, type, hooked_module, hooked_func, hooking_module)")
        self.cur.execute("create table if not exists privs(pid, priv)")

        self.cur.execute("create table if not exists kernel_mods(offset unique, name, base, size, fullname)")
        self.cur.execute("create table if not exists kernel_mods_impfunc(base, iat, call, mod_name, func_name)")
        self.cur.execute("create table if not exists kernel_mods_strings(base, string)")
        self.cur.execute("create table if not exists kernel_mods_irp(base, mj_func, addr, mod_name)")
        self.cur.execute("create table if not exists kernel_mods_callbacks(base, type, callback, detail)")
        self.cur.execute("create table if not exists kernel_mods_timers(base, offset, duetime, period, signaled, routine)")

        self.cur.execute("create table if not exists regpath(path unique)")
        self.cur.execute("create table if not exists shimcache(path, modified)")
        self.cur.execute("create table if not exists service(service_name, display_name, bin_path)")
        self.cur.execute("create table if not exists ssdt_hooked(table_idx, entry_idx, syscall_ptr, syscall_name, hooking_mod_name, inline_hooked)")
        self.cur.execute("create table if not exists files(offset, inode, name, extension, path, size)")

    def init_db(self, f_erase):
        global g_cache_path
        image_url = self._config.opts["location"]
        image_path = urllib.url2pathname(image_url.split('///')[1])

        if self._config.cache_path is None:
            g_cache_path = os.path.join(os.path.dirname(image_path), os.path.basename(image_path).split('.')[0] + '_cache')
            if not os.path.exists(g_cache_path):
                os.mkdir(g_cache_path)
        else:
            g_cache_path = self._config.cache_path
        self.db = sqlite3.connect(os.path.join(g_cache_path, os.path.basename(image_path).split('.')[0] + '.db'))
        self.cur = self.db.cursor()

        # version is null or not matched, make new tables
        self.cur.execute("select * from sqlite_master where type='table'")
        if self.cur.fetchone() == None:
            self.make_tables()
        else:
            self.cur.execute("select * from version")
            db_version = self.cur.fetchone()[0]
            if db_version != g_version or f_erase:
                self.clear_tables()
                self.make_tables()
            else:
                debug.info("Results in existing database loaded")

    def parse_cmdline(self, process):
        debug.debug(process.ImageFileName)
        #if (str(process.ImageFileName) != "System") and (not isinstance(process.Peb, obj.NoneObject)):
        if not isinstance(process.Peb.ProcessParameters.CommandLine.v(), obj.NoneObject):
            debug.debug('Hi pid={0}'.format(process.UniqueProcessId))
            cmdline = str(process.Peb.ProcessParameters.CommandLine).lower()
            debug.debug('name="{0}", cmdline="{1}" (pid{2})'.format(process.ImageFileName, cmdline or None, process.UniqueProcessId))
            if cmdline is not None:
                name_idx = cmdline.find(str(process.ImageFileName).lower())
                debug.debug('name_idx={0}'.format(name_idx))
                if name_idx != -1:
                    a = re.search(r'\.exe|\.msi|\.ocx|\.dll|\.cab|\.cat|\.js|\.vbs|\.scr', cmdline) # any other?
                    if a is not None:
                        debug.debug("name='{0}', path='{1}', arg='{2}' (pid{3})".format(process.ImageFileName, cmdline[:a.end()].strip('" '), cmdline[a.end():].strip('" '), process.UniqueProcessId))
                        return cmdline[:a.end()].strip('" '), cmdline[a.end():].strip('" ')
        return 'none', 'none'

    # based on psxview
    def extract_all_active_procs(self, not_carve):
        kernel_space = utils.load_as(self._config)
        flat_space = utils.load_as(self._config, astype = 'physical')
        self.cur.execute("select count(*) from hidden")
        carved = self.cur.fetchone()[0]

        procs = []
        if carved > 0:
            self.cur.execute("select offset from hidden")
            for record in self.cur.fetchall():
                if isinstance(self.virtual_process_from_physical_offset(kernel_space, record[0]), obj.NoneObject):
                    procs.append(obj.Object("_EPROCESS", offset = record[0], vm = flat_space))
                else:
                    procs.append(self.virtual_process_from_physical_offset(kernel_space, record[0]))
            #return [self.virtual_process_from_physical_offset(kernel_space, record[0]) for record in self.cur.fetchall()]
            #return [obj.Object("_EPROCESS", offset = record[0], vm = flat_space) for record in self.cur.fetchall()]
            #return [obj.Object("_EPROCESS", offset = record[0], vm = kernel_space) for record in self.cur.fetchall()]
        else:
            records = []
            procs = []
            if not_carve:
                debug.info('getting processes from linked list... (-n option enabled)')
                procs = list(tasks.pslist(kernel_space))
                for proc in procs:
                    cmdline = proc.Peb.ProcessParameters.CommandLine.v() or ''
                    offset = kernel_space.vtop(proc.obj_offset)
                    records.append((proc.UniqueProcessId.v(), False, offset, cmdline))
            else:
                debug.info("[time-consuming task] extracting all processes including hidden/dead ones...")
                all_tasks = list(tasks.pslist(kernel_space))
                ps_sources = {}
                ps_sources['pslist'] = self.check_pslist(all_tasks)
                ps_sources['psscan'] = self.check_psscan()
                #ps_sources['thrdproc'] = self.check_thrdproc(kernel_space)
                ps_sources['pspcid'] = self.check_pspcid(kernel_space)

                seen_offsets = []
                pids = []
                for source in ps_sources.values():
                    for offset in source.keys():
                        if offset not in seen_offsets:
                            seen_offsets.append(offset)
                            #if source[offset].ExitTime != 0: # exclude dead process even if it is included in process list
                            #if (source[offset].ExitTime != 0) and (not ps_sources['pslist'].has_key(offset)): # exclude dead process not included in process list <- cannot resolve from ethread!
                            #    continue
                            if isinstance(self.virtual_process_from_physical_offset(kernel_space, offset), obj.NoneObject):
                                ep = obj.Object("_EPROCESS", offset = offset, vm = flat_space)
                            else:
                                ep = self.virtual_process_from_physical_offset(kernel_space, offset)
                            if source[offset].UniqueProcessId not in pids: # cross view in crashdump file seems to be buggy (duplicated processes) :-(
                                result = not (ps_sources['pslist'].has_key(offset) and ps_sources['psscan'].has_key(offset) and ps_sources['pspcid'].has_key(offset))
                                if result == True and source[offset].ExitTime != 0:
                                    # I checked there were some dead processes without exit time, but I don't know other methods to judge them...
                                    result = False
                                cmdline = ep.Peb.ProcessParameters.CommandLine.v() or ''
                                if isinstance(ep.UniqueProcessId.v(), obj.NoneObject):
                                    debug.warning('skipping NoneObject from flat_space')
                                    continue
                                #pid = 0 if isinstance(ep.UniqueProcessId.v(), obj.NoneObject) else ep.UniqueProcessId.v()
                                records.append((ep.UniqueProcessId.v(), bool(result), offset, cmdline))
                                procs.append(ep)
                                pids.append(ep.UniqueProcessId)

            self.cur.executemany("insert or ignore into hidden values (?, ?, ?, ?)", records)
            debug.debug('{0} procs carved'.format(len(procs)))
        return procs

    def extract_all_loaded_kernel_mods(self):
        self.cur.execute("select count(*) from kernel_mods")
        cnt = self.cur.fetchone()[0]
        kernel_space = utils.load_as(self._config)

        if cnt > 0:
            self.cur.execute("select offset from kernel_mods")
            return [obj.Object("_LDR_DATA_TABLE_ENTRY", offset = record[0], vm = kernel_space) for record in self.cur.fetchall()]
        else:
            # currently get kernel modules from linked list because the result of modscan is noisy. need to improve for hidden malicious modules in the future
            debug.info("[time-consuming task] extracting all loaded kernel modules...")
            mods = list(win32.modules.lsmod(kernel_space))
            #records = [(mod.obj_offset, str(mod.BaseDllName  or ''), mod.DllBase.v(), mod.SizeOfImage.v(), str(mod.FullDllName or '')) for mod in mods]
            records = [(str(mod.obj_offset), str(mod.BaseDllName  or ''), str(mod.DllBase.v()), mod.SizeOfImage.v(), str(mod.FullDllName or '')) for mod in mods]
            self.cur.executemany("insert or ignore into kernel_mods values (?, ?, ?, ?, ?)", records)
            #for record in records:
            #    print record
            #    self.cur.execute("insert or ignore into kernel_mods values (?, ?, ?, ?, ?)", record)
            return mods

    def filter_mods(self, mods):
        if self._config.kmod is not None:
            try:
                modlist = [m.lower() for m in self._config.kmod.split(',')]
            except ValueError:
                debug.error("Invalid kmod option {0}".format(self._config.kmod))

            filtered_mods = [mod for mod in mods if str(mod.BaseDllName  or '').lower() in modlist]
            if len(filtered_mods) == 0:
                debug.error("Cannot find kernel module {0}.".format(self._config.kmod))
            return filtered_mods
        return mods

    def calculate(self):
        # load IOCs
        scanner = IOC_Scanner()
        if self._config.ioc_dir is None:
            debug.error("You should specify IOCs directory")
        scanner.insert(self._config.ioc_dir)

        # display mode
        if self._config.show:
            definitions = scanner.display()
            yield definitions
        else:
            self.init_db(self._config.erase)
            scanner.prepare(self.cur, self._config)
            procs = [None]
            kmods = [None]
            if scanner.with_item_all('Process'):
                with Timer() as t:
                    procs = self.extract_all_active_procs(self._config.not_carve)
                debug.debug("=> elapsed scan: {0}s for process carving".format(t.secs))
                self.total_secs += t.secs
                #print procs
                # pre-generated process entries in db for all updated tasks (e.g., netinfo)
                for process in self.filter_tasks(procs):
                    #pid = 0 if isinstance(process.UniqueProcessId.v(), obj.NoneObject) else process.UniqueProcessId.v()
                    self.cur.execute("insert or ignore into done values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (process.UniqueProcessId.v(), False, False, False, False, False, False, False, False, False))
            if scanner.with_item_all('Driver'):
                kmods = self.extract_all_loaded_kernel_mods()

            if len(procs) > 1:
                debug.info('{0} processes found'.format(len(procs)))
            if len(kmods) > 1:
                debug.info('{0} kernel modules found'.format(len(kmods)))
            self.db.commit() # save procs/mods into db

            for iocid in scanner.iocs:
                debug.info('Scanning iocid={0}'.format(iocid))
                if (not scanner.with_item(iocid, 'Process')) and (not scanner.with_item(iocid, 'Driver')):
                    debug.debug('Scanning... (Process=None, Driver=None)')
                    with Timer() as t:
                        result = scanner.scan(iocid, None, None)
                    debug.debug("=> elapsed scan: {0}s".format(t.secs))
                    self.total_secs += t.secs
                    if result != '':
                        yield None, None, result
                elif scanner.with_item(iocid, 'Process') and (not scanner.with_item(iocid, 'Driver')):
                    debug.debug('Scanning... (Driver=None)')
                    for process in self.filter_tasks(procs):
                        with Timer() as t:
                            result = scanner.scan(iocid, process, None)
                        debug.debug("=> elapsed scan: {0}s, pid={1}".format(t.secs, process.UniqueProcessId))
                        self.total_secs += t.secs
                        if result != '':
                            yield process, None, result
                elif (not scanner.with_item(iocid, 'Process')) and scanner.with_item(iocid, 'Driver'):
                    debug.debug('Scanning... (Process=None)')
                    for kmod in self.filter_mods(kmods):
                        with Timer() as t:
                            result = scanner.scan(iocid, None, kmod)
                        debug.debug("=> elapsed scan: {0}s, kmod={1}, kmod_base=0x{2:x}".format(t.secs, str(kmod.BaseDllName  or ''), kmod.DllBase))
                        self.total_secs += t.secs
                        if result != '':
                            yield None, kmod, result
                else:
                    debug.warning('Combination of ProcessItem and DriverItem will take very long time. If possible, define separately or specify PID/kmod.')
                    debug.debug('Scanning...')
                    for kmod in self.filter_mods(kmods):
                        for process in self.filter_tasks(procs):
                            with Timer() as t:
                                result = scanner.scan(iocid, process, kmod)
                            pid = ', pid={0}'.format(process.UniqueProcessId) if process is not None else ''
                            kmod_name = ', kmod={0}'.format(str(kmod.BaseDllName  or '')) if kmod is not None else ''
                            debug.debug("=> elapsed scan: {0}s{1}(base=0x{2:x}){3}".format(t.secs, kmod_name, kmod.DllBase, pid))
                            self.total_secs += t.secs
                            if result != '':
                                yield process, kmod, result
                self.db.commit()

    def render_text(self, outfd, data):
        if self._config.show:
            for definitions in data:
                outfd.write(definitions)
            outfd.write('++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n')
        else:
            for process, kmod, ioc_result in data:
                outfd.write('***************************************************************\n')
                outfd.write(ioc_result)
                if process is not None:
                    outfd.write("Note: ProcessItem was evaluated only in {0} (Pid={1})\n".format(process.ImageFileName, process.UniqueProcessId))
                if kmod is not None:
                    outfd.write("Note: DriverItem was evaluated only in {0} (base=0x{1:x})\n".format(str(kmod.BaseDllName  or ''), kmod.DllBase))
                outfd.write('***************************************************************\n')

            self.db.commit()
            self.cur.close()
            debug.info("=> elapsed scan total: about {0} s".format(self.total_secs))