Commits

Daniel Plohmann committed 1787602 Merge

Merged YaraScanner into master

  • Participants
  • Parent commits 8a4682a, bfcfaa5

Comments (0)

Files changed (11)

 from idascope.core.DocumentationHelper import DocumentationHelper
 from idascope.core.WinApiProvider import WinApiProvider
 from idascope.core.CryptoIdentifier import CryptoIdentifier
+from idascope.core.YaraScanner import YaraScanner
 from idascope.core.IdaProxy import IdaProxy
 from idascope.widgets.FunctionInspectionWidget import FunctionInspectionWidget
 from idascope.widgets.WinApiWidget import WinApiWidget
 from idascope.widgets.CryptoIdentificationWidget import CryptoIdentificationWidget
+from idascope.widgets.YaraScannerWidget import YaraScannerWidget
 
 ################################################################################
 # Core of the IDAscope GUI.
 
 HOTKEYS = None
 IDASCOPE = None
-NAME = "simpliFiRE.IDAscope v1.0"
+NAME = "simpliFiRE.IDAscope v1.1"
 
 
 class IDAscopeForm(PluginForm):
         """
         time_before = time.time()
         print ("[/] setting up shared modules...")
-        self.semantic_identifier = SemanticIdentifier(self.config)
-        self.crypto_identifier = CryptoIdentifier()
         self.documentation_helper = DocumentationHelper(self.config)
+        self.semantic_identifier = SemanticIdentifier(self.config)
         self.winapi_provider = WinApiProvider(self.config)
+        self.crypto_identifier = CryptoIdentifier()
+        self.yara_scanner = YaraScanner(self.config)
         self.ida_proxy = IdaProxy()
         print ("[\\] this took %3.2f seconds.\n" % (time.time() - time_before))
 
         self.idascope_widgets.append(FunctionInspectionWidget(self))
         self.idascope_widgets.append(WinApiWidget(self))
         self.idascope_widgets.append(CryptoIdentificationWidget(self))
+        self.idascope_widgets.append(YaraScannerWidget(self))
         self.setupIDAscopeForm()
         print ("[\\] this took %3.2f seconds.\n" % (time.time() - time_before))
 

File idascope/config.py

         },
     "inspection": {
         "default_semantics": "win-ring3"
+        },
+    "yara": {
+        "yara_sigs": ["C:\\yara"]
         }
 }

File idascope/core/YaraScanner.py

+#!/usr/bin/python
+########################################################################
+# Copyright (c) 2014
+# Daniel Plohmann <daniel.plohmann<at>gmail<dot>com>
+# Alexander Hanel <alexander.hanel<at>gmail<dot>com>
+# All rights reserved.
+########################################################################
+#
+#  This file is part of IDAscope
+#
+#  IDAscope is free software: you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful, but
+#  WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see
+#  <http://www.gnu.org/licenses/>.
+#
+########################################################################
+# Credits:
+# * Christopher Kannen for contributing an independent loader for
+#   YARA rules which allows to display unmatched rules and
+#   content of rule files as loaded by yara-python
+########################################################################
+
+import os
+import re
+import time
+
+try:
+    import yara
+except ImportError:
+    print("[-] ERROR: Could not import YARA (not installed?), scanner disabled.")
+    yara = None
+
+from IdaProxy import IdaProxy
+import idascope.core.helpers.Misc as Misc
+from idascope.core.helpers.YaraRuleLoader import YaraRuleLoader
+from idascope.core.helpers.YaraRule import YaraRule
+
+
+class YaraScanner():
+    """
+    A module to analyze and explore an IDB for semantics. For a set of API names, references to these
+    are identified and used for creating context and allowing tagging of them.
+    """
+
+    def __init__(self, idascope_config):
+        print ("[|] loading YaraScanner")
+        self.os = os
+        self.re = re
+        self.time = time
+        self.yara = yara
+        self.YaraRule = YaraRule
+        self.ida_proxy = IdaProxy()
+        self.yrl = YaraRuleLoader()
+        # fields
+        self.idascope_config = idascope_config
+        self.num_files_loaded = 0
+        self._compiled_rules = []
+        self._yara_rules = []
+        self._results = []
+        self.segment_offsets = []
+
+    def getResults(self):
+        return self._results
+
+    def load_rules(self):
+        if not self.yara:
+            return
+        self.num_files_loaded = 0
+        self._compiled_rules = []
+        self._yara_rules = []
+        for yara_path in self.idascope_config.yara_sig_folders:
+            self._load_recursive(yara_path)
+
+    def _load_recursive(self, yara_path):
+        if os.path.isfile(yara_path):
+            self._load_file(yara_path)
+        elif os.path.isdir(yara_path):
+            for dirpath, dirnames, filenames in os.walk(yara_path):
+                for filename in filenames:
+                    filepath = dirpath + os.sep + filename
+                    self._load_file(filepath)
+
+    def _load_file(self, filepath):
+        try:
+            rules = yara.compile(filepath)
+            self._compiled_rules.append(rules)
+            rules_from_file = self.yrl.loadRulesFromFile(filepath)
+            self._yara_rules.extend(rules_from_file)
+            print "loading rules from file: %s (%d)" % (filepath, len(rules_from_file))
+            if rules:
+                self.num_files_loaded += 1
+        except:
+            print "[!] Could not load yara rules from file: %s" % filepath
+
+    def scan(self):
+        if not self.yara:
+            print "[!] yara-python not available, please install it from (http://plusvic.github.io/yara/)"
+            return
+        memory, offsets = self._get_memory()
+        self.segment_offsets = offsets
+        self._results = []
+        matches = []
+        print "[!] Performing YARA scan..."
+        for rule in self._compiled_rules:
+            matches.append(rule.match(data=memory, callback=self._result_callback))
+        if len(matches) == 0:
+            print "  [-] no matches. :("
+
+    def _get_memory(self):
+        result = ""
+        segment_starts = [ea for ea in self.ida_proxy.Segments()]
+        offsets = []
+        start_len = 0
+        for start in segment_starts:
+            end = self.ida_proxy.SegEnd(start)
+            for ea in Misc.lrange(start, end):
+                result += chr(self.ida_proxy.Byte(ea))
+            offsets.append((start, start_len, len(result)))
+            start_len = len(result)
+        return result, offsets
+
+    def _result_callback(self, data):
+        adjusted_offsets = []
+        for string in data["strings"]:
+            adjusted_offsets.append((self._translateMemOffsetToVirtualAddress(string[0]), string[1], string[2]))
+        data["strings"] = adjusted_offsets
+        if data["matches"]:
+            print "  [+] YARA Match for signature: %s" % data["rule"]
+        result_rule = None
+        for rule in self._yara_rules:
+            if rule.rule_name == data["rule"]:
+                result_rule = rule
+        if not result_rule:
+            result_rule = self.YaraRule()
+        result_rule.match_data = data
+        self._results.append(result_rule)
+
+        self.yara.CALLBACK_CONTINUE
+
+    def _translateMemOffsetToVirtualAddress(self, offset):
+        va_offset = 0
+        for seg in self.segment_offsets:
+            if seg[1] < offset < seg[2]:
+                va_offset = seg[0] + (offset - seg[1])
+        return va_offset

File idascope/core/helpers/YaraRule.py

+#!/usr/bin/python
+########################################################################
+# Copyright (c) 2014
+# Christopher Kannen <ckannen<at>uni-bonn<dot>de>
+# Daniel Plohmann <daniel.plohmann<at>gmail<dot>com>
+# All rights reserved.
+########################################################################
+#
+#  This file is part of IDAscope
+#
+#  IDAscope is free software: you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful, but
+#  WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see
+#  <http://www.gnu.org/licenses/>.
+#
+########################################################################
+#
+########################################################################
+
+from YaraStatusController import StatusController
+
+
+class YaraRule(object):
+    """ Yara Rule class """
+
+    def __init__(self):
+        self.statusController = StatusController()
+        self.filename = ""
+        # set raw data of rule content, to be parsed by analyze* methods
+        self.raw_header = ""
+        self.raw_header_cleaned = ""
+        self.raw_meta = ""
+        self.raw_meta_cleaned = ""
+        self.raw_strings = ""
+        self.raw_strings_cleaned = ""
+        self.raw_condition = ""
+        self.raw_condition_cleaned = ""
+        # rule header
+        self.is_global = False
+        self.is_private = False
+        self.rule_name = ""
+        self.rule_description = []
+        # meta
+        self.meta = []
+        # strings
+        self.strings = []
+        #condition
+        self.condition = ""
+        # match data as provided by yara-python
+        self.match_data = {}
+
+    def analyze(self):
+        self._analyzeHeader()
+        self._analyzeMeta()
+        self._analyzeStrings()
+        self._analyzeCondition()
+
+    def _linebreakAndTabsToSpace(self, content):
+        """ replace all linebreaks and tabs by spaces """
+        new_content = ""
+        for i in xrange(len(content)):
+            if (content[i] == "\r"):
+                new_content += " "
+            elif (content[i] == "\n"):
+                new_content += " "
+            elif (content[i] == "\t"):
+                new_content += " "
+            else:
+                new_content += content[i]
+        return new_content
+
+    def _analyzeHeader(self):
+        """ analyze Yara rule header, find keywords PRIVATE and GLOBAL, get rule NAME and DESCRIPTION """
+        self.statusController.reset()
+        # delete tabs and linebreaks and then split rule header into single words
+        raw_header_cleaned = self._linebreakAndTabsToSpace(self.raw_header_cleaned)
+        raw_header_cleaned = raw_header_cleaned.replace(":", " : ")
+        # analyze words
+        for header_word in raw_header_cleaned.split(" "):
+            if header_word == "private":
+                self.is_private = True
+            elif header_word == "global":
+                self.is_global = True
+            elif header_word == "rule":
+                self.statusController.status = "find_rule_name"
+            elif header_word == ":":
+                self.statusController.status = "find_rule_description"
+            elif self.statusController.status == "find_rule_name" and header_word != "":
+                self.rule_name = header_word
+            elif self.statusController.status == "find_rule_description" and header_word != "":
+                self.rule_description.append(header_word)
+
+    def _analyzeMeta(self):
+        """ analyze meta section of Yara rule and save tuples (meta name, meta value) in list of meta entries """
+        # current meta entry
+        meta_name = ""
+        meta_content = ""
+        # status ("find_name", "name", "find_field_value", "value")
+        self.statusController.reset("find_name")
+        # read meta string and replace line breaks and tabs
+        raw_meta = self.raw_meta
+        raw_meta_cleaned = self._linebreakAndTabsToSpace(self.raw_meta_cleaned)
+        # check if meta section exists
+        if (len(raw_meta_cleaned) == 0) or (raw_meta_cleaned.find(":") == -1):
+            return
+        # insert an additional whitespace at the end as end delimiter to handle compact rules
+        raw_meta += " "
+        raw_meta_cleaned += " "
+        # split at first colon
+        temp, meta_body_cleaned = raw_meta_cleaned.split(":", 1)
+        meta_body = raw_meta[len(temp) + 1:]
+        # go through file and split it in Yara rules and them in sections
+        for i in xrange(len(meta_body_cleaned)):
+            # find beginning of meta entry name
+            if self.statusController.controlStatus("find_name", not self.statusController.findKeyword(meta_body_cleaned, i, " "), "name"):
+                pass
+            # find end of meta entry name
+            elif self.statusController.controlStatus("name", self.statusController.findKeyword(meta_body_cleaned, i, "="), "find_field_value"):
+                continue
+            # find beginning of meta entry value
+            if self.statusController.controlStatus("find_field_value", not self.statusController.findKeyword(meta_body_cleaned, i, " "), "value"):
+                # skip first letter by continue if value is a string
+                if (meta_body_cleaned[i] == "\""):
+                    continue
+            # find end of meta entry value
+            if self.statusController.controlStatus("value", i == len(meta_body_cleaned) - 1
+                    or self.statusController.findKeyword(meta_body_cleaned, i, " ") or self.statusController.findKeyword(meta_body_cleaned, i, "\""), "find_name"):
+                if not self.statusController.findKeyword(meta_body_cleaned, i, " ") and i == len(meta_body_cleaned) - 1:
+                    meta_content += meta_body[i]
+                if self.statusController.findKeyword(meta_body_cleaned, i, " ") or i == len(meta_body_cleaned) - 1:
+                    if meta_content == "true":
+                        meta_content = True
+                    elif meta_content == "false":
+                        meta_content = False
+                    elif meta_content.isdigit():
+                        meta_content = int(meta_content)
+                meta_name = meta_name.strip()
+                self.meta.append([meta_name, meta_content])
+                # reset variables
+                meta_name = ""
+                meta_content = ""
+                continue
+            # copy content in meta name or meta value
+            if (self.statusController.status == "name"):
+                meta_name += meta_body[i]
+            if (self.statusController.status == "value"):
+                meta_content += meta_body[i]
+
+    def _identifyStringType(self, indicator):
+        """ identify type of string (text, regex, byte array) by it's first character """
+        if indicator == "\"":
+            return "text"
+        if indicator == "/":
+            return "regular_expression"
+        if indicator == "{":
+            return "byte_array"
+        raise ValueError("Invalid string type indicator: %s" % indicator)
+
+    def _checkStringValueTerminator(self, string_type, char):
+        terminator_types = [("text", "\""), ("regular_expression", "/"), ("byte_array", "}")]
+        return (string_type, char) in terminator_types
+
+    def _analyzeStrings(self):
+        """ analyze strings section of Yara rule and save tuples (string name, string value, string type) in list of string entries """
+        # current string variable
+        var_name = ""
+        var_content = ""
+        var_keywords = []
+        # status ("find_name", "name", "find_field_value", "value")
+        self.statusController.reset("find_name")
+
+        # read strings string and replace line breaks and tabs
+        raw_strings = self.raw_strings
+        raw_strings_cleaned = self._linebreakAndTabsToSpace(self.raw_strings_cleaned)
+        # check if meta section exists
+        if ((len(raw_strings_cleaned) == 0) or (raw_strings_cleaned.find(":") == -1)):
+            return
+        # insert an additional whitespace at the end as end delimiter to handle compact rules
+        raw_strings += " "
+        raw_strings_cleaned += " "
+        # split at first colon
+        temp, raw_strings_cleaned = raw_strings_cleaned.split(":", 1)
+        raw_strings = raw_strings[len(temp) + 1:]
+        # go through file and split it in Yara rules and them in sections
+        skip_i = 0
+        for i in xrange(len(raw_strings_cleaned)):
+            # skip character(s)
+            if (skip_i > 0):
+                skip_i -= 1
+                continue
+            # find beginning of string variable name
+            if self.statusController.controlStatus("find_name", not self.statusController.findKeyword(raw_strings_cleaned, i, " "), "name"):
+                pass
+            # find end of string variable name
+            elif self.statusController.controlStatus("name", self.statusController.findKeyword(raw_strings_cleaned, i, "="), "find_field_value"):
+                continue
+            # find beginning of string variable value
+            if self.statusController.controlStatus("find_field_value", not self.statusController.findKeyword(raw_strings_cleaned, i, " "), "value"):
+                string_variable_type = self._identifyStringType(raw_strings_cleaned[i])
+                continue
+            # find end of string variable value
+            if (self.statusController.status == "value"):
+                # check for all 3 types of strings (text, regex, byte array) if the string is complete
+                # and save string variable if string is complete
+                if self._checkStringValueTerminator(string_variable_type, raw_strings_cleaned[i]):
+                    self.statusController.status = "stringModifier"
+                    continue
+            # look for string modification keywords after value ("wide", "ascii", "nocase", "fullword")
+            string_modifiers = ["wide", "ascii", "nocase", "fullword"]
+            for modifier in string_modifiers:
+                if self.statusController.controlStatus("stringModifier", self.statusController.findKeyword(raw_strings_cleaned, i, modifier), "stringModifier"):
+                    var_keywords.append(modifier)
+                    skip_i = len(modifier)
+                    break
+            if (skip_i > 0):
+                continue
+            # check if there is any character after string, that is not part of a keyword and is no blank
+            self.statusController.controlStatus("stringModifier", not self.statusController.findKeyword(raw_strings_cleaned, i, " "), "saveString")
+            # save string if this the end of strings section is reached
+            if i == len(raw_strings_cleaned) - 1:
+                self.statusController.status = "saveString"
+            # save string
+            if (self.statusController.status == "saveString"):
+                    # delete white spaces
+                    var_name = var_name.strip()
+                    if (string_variable_type == "regular_expression") or (string_variable_type == "byte_array"):
+                        var_content = var_content.strip()
+                    # save tuple in strings list
+                    self.strings.append((string_variable_type, var_name, var_content, var_keywords))
+                    # reset status
+                    self.statusController.status = "name"
+                    # reset variables
+                    var_name = ""
+                    var_content = ""
+                    var_keywords = []
+            # copy content in string name or string value
+            if (self.statusController.status == "name"):
+                var_name += raw_strings[i]
+            if (self.statusController.status == "value"):
+                var_content += raw_strings[i]
+
+    def _analyzeCondition(self):
+        """ analyze Yara rule condition """
+        # delete tabs and linebreaks
+        temp_condition = self._linebreakAndTabsToSpace(self.raw_condition_cleaned)
+        # check if meta section exists
+        if (len(temp_condition) == 0) or (temp_condition.find(":") == -1):
+            return
+        # split at first colon
+        temp, temp_condition = temp_condition.split(":", 1)
+        # delete white spaces at beginning and end of string
+        temp_condition = temp_condition.strip()
+        # replace multiple spaces in string
+        temp_condition_len = 0
+        while (temp_condition_len != len(temp_condition)):
+            temp_condition_len = len(temp_condition)
+            temp_condition = temp_condition.replace("  ", " ")
+        # save condition in Yara rule
+        self.condition = temp_condition
+
+    def __str__(self):
+        if not self.rule_name:
+            return "Failed to load rule through YaraRuleLoader. Please be so kind and report this back for a bug fix! :)"
+        start_delimiters = {"text": "\"", "regular_expression": "/ ", "byte_array": "{ "}
+        end_delimiters = {"text": "\"", "regular_expression": " /", "byte_array": " }"}
+        result = ""
+        result += "global " if self.is_global else ""
+        result += "private " if self.is_private else ""
+        result += "rule " + self.rule_name
+        if self.rule_description:
+            result += " : " + " ".join(self.rule_description)
+        result += "\n{\n"
+        if self.meta:
+            result += "    meta:\n"
+            for meta_line in self.meta:
+                if isinstance(meta_line[1], str):
+                    result += " " * 8 + "%s = \"%s\"\n" % (meta_line[0], meta_line[1])
+                else:
+                    result += " " * 8 + "%s = %s\n" % (meta_line[0], meta_line[1])
+            result += "\n"
+        if self.strings:
+            result += "    strings:\n"
+            for string_line in self.strings:
+                result += " " * 8 + "%s = %s%s%s %s\n" % (string_line[1],
+                                                       start_delimiters[string_line[0]],
+                                                       string_line[2],
+                                                       end_delimiters[string_line[0]], " ".join(string_line[3]))
+            result += "\n"
+        result += "    condition:\n"
+        result += " " * 8 + "%s\n" % self.condition
+        result += "}"
+        return result

File idascope/core/helpers/YaraRuleLoader.py

+#!/usr/bin/python
+########################################################################
+# Copyright (c) 2014
+# Christopher Kannen <ckannen<at>uni-bonn<dot>de>
+# Daniel Plohmann <daniel.plohmann<at>gmail<dot>com>
+# All rights reserved.
+########################################################################
+#
+#  This file is part of IDAscope
+#
+#  IDAscope is free software: you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful, but
+#  WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see
+#  <http://www.gnu.org/licenses/>.
+#
+########################################################################
+#
+########################################################################
+
+from YaraRule import YaraRule
+from YaraStatusController import StatusController
+
+
+class YaraRuleLoader(object):
+    """ Yara Rule Loader class """
+
+    def __init__(self):
+        """ init Yara Rule Loader Object """
+        self.statusController = StatusController()
+
+    def loadRulesFromFile(self, filename):
+        """ load content of file (1) """
+        content = ""
+        # read file
+        with open(filename, 'r') as f_input:
+            content = f_input.read()
+        #clean content
+        content_cleaned = self._cleanContent(content)
+        # split content in Yara Rules
+        return self._splitYaraRules(content, content_cleaned, filename)
+
+    def _cleanContent(self, content):
+        """ clean content, replace comments by spaces, replace strings by underlines """
+        # current status while going through content ("", "string", "comment_multiline", "comment_singleline")
+        self.statusController.reset()
+        # result
+        result = ""
+        # go through file and copy everything but comments and strings, instead write blanks or underlines
+        skip_i = 0
+        for i in xrange(len(content)):
+            # skip character(s)
+            if (skip_i > 0):
+                skip_i -= 1
+                continue
+
+            ## find strings - text
+            # find beginnig of string
+            if self.statusController.controlStatus("", self.statusController.findKeyword(content, i, "\""), "string_text"):
+                result += "\""
+                continue
+            # skip next character when finding the escape character \ inside string
+            if self.statusController.controlStatus("string_text", self.statusController.findKeyword(content, i, "\\"), None):
+                result += "__"
+                skip_i = 1
+                continue
+            # find end of string
+            if self.statusController.controlStatus("string_text", self.statusController.findKeyword(content, i, "\""), ""):
+                result += "\""
+                continue
+            ## find strings - regex
+            # find beginnig of string
+            if self.statusController.controlStatus("", self.statusController.findKeyword(content, i, "/")
+                    and not self.statusController.findKeyword(content, i, "//")  and not self.statusController.findKeyword(content, i, "/*"), "string_regex"):
+                result += "/"
+                continue
+            # skip next character when finding the escape character \ inside string
+            if self.statusController.controlStatus("string_regex", self.statusController.findKeyword(content, i, "\\"), None):
+                result += "__"
+                skip_i = 1
+                continue
+            # find end of string
+            if self.statusController.controlStatus("string_regex", self.statusController.findKeyword(content, i, "/"), ""):
+                result += "/"
+                continue
+
+            ## find multi line comments
+            # find beginnig of comment
+            if self.statusController.controlStatus("", self.statusController.findKeyword(content, i, "/*"), "comment_multiline"):
+                result += "  "
+                skip_i = 1
+                continue
+            # find end of string
+            if self.statusController.controlStatus("comment_multiline", self.statusController.findKeyword(content, i, "*/"), ""):
+                result += "  "
+                skip_i = 1
+                continue
+
+            ## find single line comments
+            # find beginnig of comment
+            if self.statusController.controlStatus("", self.statusController.findKeyword(content, i, "//"), "comment_singleline"):
+                result += "  "
+                skip_i = 1
+                continue
+            # find end of comment by finding end of line \r
+            if self.statusController.controlStatus("comment_singleline", self.statusController.findKeyword(content, i, "\r"), ""):
+                result += "\r"
+                continue
+            # find end of comment by finding end of line \n
+            if self.statusController.controlStatus("comment_singleline", self.statusController.findKeyword(content, i, "\n"), ""):
+                result += "\n"
+                continue
+
+            ## copy content
+            # copy content if this is neither a comment nor a string, else add spaces or underlines
+            if (self.statusController.status == ""):
+                result += content[i]
+            elif (self.statusController.status == "string_text"):
+                result += "_"
+            elif (self.statusController.status == "string_regex"):
+                result += "_"
+            else:
+                result += " "
+
+        # return content without comments and strings
+        return result
+
+    def _splitYaraRules(self, content, content_cleaned, filename):
+        """ get all Yara rules split in sections (header, meta, strings, condition) """
+        # result, list of Yara rules
+        yara_rules = []
+        # sections of current Yara rule
+        current_rule = YaraRule()
+        # status ("", "header", "meta", "strings", "condition"), file starts in Yara rule header, so status is "header"
+        self.statusController.reset("header")
+        # list of characters, one of them must stand in front of every section keyword
+        needed_chars = [" ", "\r", "\n", "\t", "\"", "/", "{", "}"]
+
+        # go through file and split it in Yara rules and them in sections
+        for i in xrange(len(content_cleaned)):
+            ## header
+            # find end of header section
+            if self.statusController.controlStatus("header", self.statusController.findKeyword(content_cleaned, i, "{"), ""):
+                continue
+            # copy header
+            if (self.statusController.status == "header"):
+                current_rule.raw_header += content[i]
+                current_rule.raw_header_cleaned += content_cleaned[i]
+
+            ## meta
+            # find beginning of meta section
+            self.statusController.controlStatus("", self.statusController.findKeyword(content_cleaned, i, "meta", needed_chars), "meta")
+            # find end of meta section
+            self.statusController.controlStatus("meta", self.statusController.findKeyword(content_cleaned, i, "strings", needed_chars), "")
+            self.statusController.controlStatus("meta", self.statusController.findKeyword(content_cleaned, i, "condition", needed_chars), "")
+            # copy meta
+            if (self.statusController.status == "meta"):
+                current_rule.raw_meta += content[i]
+                current_rule.raw_meta_cleaned += content_cleaned[i]
+
+            ## strings
+            # find beginning of strings section
+            self.statusController.controlStatus("", self.statusController.findKeyword(content_cleaned, i, "strings", needed_chars), "strings")
+            # find end of strings section
+            self.statusController.controlStatus("strings", self.statusController.findKeyword(content_cleaned, i, "condition", needed_chars), "")
+            # copy meta
+            if (self.statusController.status == "strings"):
+                current_rule.raw_strings += content[i]
+                current_rule.raw_strings_cleaned += content_cleaned[i]
+
+            ## condition
+            # find beginning of condition section
+            self.statusController.controlStatus("", self.statusController.findKeyword(content_cleaned, i, "condition", needed_chars), "condition")
+            # find end of condition section
+            self.statusController.controlStatus("condition", self.statusController.findKeyword(content_cleaned, i, "}"), "endOfRule")
+            # copy meta
+            if (self.statusController.status == "condition"):
+                current_rule.raw_condition += content[i]
+                current_rule.raw_condition_cleaned += content_cleaned[i]
+
+            ## find end of rule
+            # save rule and reinit parsing
+            if self.statusController.controlStatus("endOfRule", True, "header"):
+                # add fully parsed rule to list and create next rule
+                yara_rules.append(current_rule)
+                # analyze Yara rule
+                current_rule.filename = filename
+                current_rule.analyze()
+                current_rule = YaraRule()
+
+        # return list of Yara rules
+        return yara_rules

File idascope/core/helpers/YaraStatusController.py

+#!/usr/bin/python
+########################################################################
+# Copyright (c) 2014
+# Christopher Kannen <ckannen<at>uni-bonn<dot>de>
+# Daniel Plohmann <daniel.plohmann<at>gmail<dot>com>
+# All rights reserved.
+########################################################################
+#
+#  This file is part of IDAscope
+#
+#  IDAscope is free software: you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful, but
+#  WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see
+#  <http://www.gnu.org/licenses/>.
+#
+########################################################################
+#
+########################################################################
+
+
+class StatusController(object):
+    """ Status Controller class """
+
+    def __init__(self):
+        """ init Status Controller Object """
+        self.status = ""
+
+    def reset(self, status=""):
+        self.status = status
+
+    def findKeyword(self, content, start_pos, keyword, needed_prefix_list=[]):
+        """ find keyword in given string at given position """
+        # check if keyword even fits in content
+        if content[start_pos:start_pos + len(keyword)] != keyword:
+            return False
+        # check if prefix is one of the needed ones
+        if needed_prefix_list != []:
+            for needed_prefix in needed_prefix_list:
+                if content[start_pos - len(needed_prefix):start_pos] == needed_prefix:
+                    return True
+            return False
+        # return True if everything is ok
+        return True
+
+    def controlStatus(self, status_condition, second_condition, new_status):
+        """ if status is in the correct state and condition is true, change status """
+        if (self.status == status_condition) and second_condition:
+            # change status, if a new status is passed
+            if (new_status != None):
+                self.status = new_status
+            return True
+        return False

File idascope/core/structures/IDAscopeConfiguration.py

         self.winapi_shortcut = "ctrl+y"
         self.winapi_load_keyword_database = False
         self.winapi_online_enabled = False
+        self.yara_sig_folders = []
         self._loadConfig(configuration)
 
     def _loadConfig(self, configuration):
         self.winapi_load_keyword_database = configuration["winapi"]["load_keyword_database"]
         self.winapi_online_enabled = configuration["winapi"]["online_enabled"]
         self.inspection_default_semantics = configuration["inspection"]["default_semantics"]
+        self.yara_sig_folders = configuration["yara"]["yara_sigs"]
 
     def _normalizePath(self, path):
         if self.os_path_normpath is None:
             + "  icon_file_path: %s\n" % self.icon_file_path \
             + "  semantics_file: %s\n" % self.semantics_file \
             + "  winapi_keywords_file: %s\n" % self.winapi_keywords_file \
-            + "  winapi_rootdir: %s" % self.winapi_rootdir
+            + "  winapi_rootdir: %s" % self.winapi_rootdir \
+            + "  yara_sigs: %s" % self.yara_sig_folders

File idascope/icons/yarascan.png

Added
New image

File idascope/widgets/FunctionInspectionWidget.py

         self.QtGui = QtGui
         self.QtCore = QtCore
         self.NumberQTableWidgetItem = NumberQTableWidgetItem
-        self. FunctionFilterDialog = FunctionFilterDialog
+        self.FunctionFilterDialog = FunctionFilterDialog
         self.central_widget = self.QtGui.QWidget()
         self.setCentralWidget(self.central_widget)
         self._createGui()

File idascope/widgets/YaraRuleDialog.py

+#!/usr/bin/python
+########################################################################
+# Copyright (c) 2012
+# Daniel Plohmann <daniel.plohmann<at>gmail<dot>com>
+# Alexander Hanel <alexander.hanel<at>gmail<dot>com>
+# All rights reserved.
+########################################################################
+#
+#  This file is part of IDAscope
+#
+#  IDAscope is free software: you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful, but
+#  WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see
+#  <http://www.gnu.org/licenses/>.
+#
+########################################################################
+
+from PySide import QtGui, QtCore
+
+
+class YaraRuleDialog(QtGui.QDialog):
+    """ oriented on: https://stackoverflow.com/a/11764475 """
+
+    def __init__(self, rule, parent=None):
+        QtGui.QDialog.__init__(self, parent)
+        # create GUI elements
+        self.rule = rule
+        self._createOkButton()
+        # glue everything together
+        # create scroll for rule text edit
+        self.scroll = QtGui.QScrollArea()
+        self.scroll.setWidgetResizable(True)
+        self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
+        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
+        self.setSizePolicy(sizePolicy)
+        scrollContents = QtGui.QWidget()
+        self.scroll.setWidget(scrollContents)
+        # create growing textedit for rule display
+        self.textLayout = QtGui.QVBoxLayout()
+        self.rule_textedit = GrowingTextEdit()
+        self.setMinimumHeight(300)
+        self.setMinimumWidth(550)
+        self.rule_textedit.setReadOnly(True)
+        self.textLayout.addWidget(self.rule_textedit)
+
+        dialog_layout = QtGui.QVBoxLayout(scrollContents)
+
+        dialog_layout.addLayout(self.textLayout)
+        dialog_layout.addLayout(self.button_layout)
+        self.setLayout(dialog_layout)
+        if self.rule:
+            self.setWindowTitle(self.tr("YARA Rule: %s (%s)" % (self.rule.rule_name, self.rule.filename)))
+            self.rule_textedit.setText(str(rule))
+        else:
+            self.setWindowTitle(self.tr("No rule selected."))
+
+    def _createOkButton(self):
+        self.button_layout = QtGui.QHBoxLayout()
+        self.ok_button = QtGui.QPushButton(self.tr("OK"))
+        self.ok_button.clicked.connect(self.accept)
+        self.button_layout.addStretch(1)
+        self.button_layout.addWidget(self.ok_button)
+        self.button_layout.addStretch(1)
+
+    def accept(self):
+        self.done(1)
+
+
+class GrowingTextEdit(QtGui.QTextEdit):
+    """ source: https://stackoverflow.com/a/11764475 """
+
+    def __init__(self, *args, **kwargs):
+        super(GrowingTextEdit, self).__init__(*args, **kwargs)
+        self.document().contentsChanged.connect(self.sizeChange)
+
+        self.heightMin = 0
+        self.heightMax = 1400
+
+    def getHeight(self):
+        return self.document().size().height()
+
+    def sizeChange(self):
+        docHeight = self.getHeight()
+        if self.heightMin <= docHeight <= self.heightMax:
+            self.setMinimumHeight(docHeight)

File idascope/widgets/YaraScannerWidget.py

+#!/usr/bin/python
+########################################################################
+# Copyright (c) 2014
+# Daniel Plohmann <daniel.plohmann<at>gmail<dot>com>
+# Alexander Hanel <alexander.hanel<at>gmail<dot>com>
+# All rights reserved.
+########################################################################
+#
+#  This file is part of IDAscope
+#
+#  IDAscope is free software: you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful, but
+#  WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see
+#  <http://www.gnu.org/licenses/>.
+#
+########################################################################
+
+from PySide import QtGui, QtCore
+from PySide.QtGui import QIcon
+
+from NumberQTableWidgetItem import NumberQTableWidgetItem
+from YaraRuleDialog import YaraRuleDialog
+
+
+class YaraScannerWidget(QtGui.QMainWindow):
+
+    def __init__(self, parent):
+        QtGui.QMainWindow.__init__(self)
+        print "[|] loading YaraScannerWidget"
+        # enable access to shared IDAscope modules
+        self.parent = parent
+        self.name = "YARA Scanner"
+        self.icon = QIcon(self.parent.config.icon_file_path + "yarascan.png")
+        # This widget relies on yara scanner for resuls and scanning as well as IdaProxy for navigation
+        self.ys = self.parent.yara_scanner
+        self.ip = self.parent.ida_proxy
+        # references to Qt-specific modules
+        self.QtGui = QtGui
+        self.QtCore = QtCore
+        self.NumberQTableWidgetItem = NumberQTableWidgetItem
+        self.YaraRuleDialog = YaraRuleDialog
+
+        self.central_widget = self.QtGui.QWidget()
+        self.setCentralWidget(self.central_widget)
+        self._createGui()
+        self._selected_rule = None
+
+    def _createGui(self):
+        """
+        Setup function for the full GUI of this widget.
+        """
+        # toolbar
+        self._createToolbar()
+        # Overview of rules and matches
+        self.result_table = None
+        self._createRulesWidget()
+        # Details for a selected rule
+        self._createResultWidget()
+        # layout and fill the widget
+        yara_layout = QtGui.QVBoxLayout()
+        splitter = self.QtGui.QSplitter(self.QtCore.Qt.Vertical)
+        q_clean_style = QtGui.QStyleFactory.create('Plastique')
+        splitter.setStyle(q_clean_style)
+        splitter.addWidget(self.rules_widget)
+        splitter.addWidget(self.result_widget)
+        yara_layout.addWidget(splitter)
+
+        self.central_widget.setLayout(yara_layout)
+
+    def _createToolbar(self):
+        """
+        Creates the toolbar, containing buttons to control the widget.
+        """
+        self._createLoadAndScanAction()
+        self.toolbar = self.addToolBar('YARA Scanner Toobar')
+        self.toolbar.addAction(self.loadAndScanAction)
+
+    def _createLoadAndScanAction(self):
+        """
+        Create an action for the scan button of the toolbar and connect it.
+        """
+        self.loadAndScanAction = QtGui.QAction(QIcon(self.parent.config.icon_file_path + "search.png"), \
+            "(Re)load YARA Signature files and scan", self)
+        self.loadAndScanAction.triggered.connect(self._onLoadAndScanButtonClicked)
+
+    def _onLoadAndScanButtonClicked(self):
+        """
+        reload yara rules and scan all segments, then present results
+        """
+        self.ys.load_rules()
+        self.ys.scan()
+        self.setRulesLabel(len(self.ys.getResults()), self.ys.num_files_loaded)
+        self.populateRulesTable()
+
+################################################################################
+# Rules GUI
+################################################################################
+
+    def _createRulesWidget(self):
+        """
+        Create the widget for the arithmetic/logic heuristic.
+        """
+        self.rules_widget = QtGui.QWidget()
+        rules_layout = QtGui.QVBoxLayout()
+        self.rules_label = self.QtGui.QLabel()
+        self.setRulesLabel(0, 0)
+
+        # rule visualization
+        self.rules_widget = QtGui.QWidget()
+        rules_layout = QtGui.QVBoxLayout()
+        self._createRuleTable()
+
+        # widget composition
+        rules_layout.addWidget(self.rules_label)
+        rules_layout.addWidget(self.rules_table)
+        self.rules_widget.setLayout(rules_layout)
+
+    def setRulesLabel(self, num_rules, num_files):
+        self.rules_label.setText("Results for %d rules loaded from %d files" % (num_rules, num_files))
+
+    def _createRuleTable(self):
+        """
+        Create the result table for displaying results yara scanning
+        """
+        self.rules_table = QtGui.QTableWidget()
+        self.populateRulesTable()
+        self.rules_table.clicked.connect(self._onRuleClicked)
+
+    def populateRulesTable(self):
+        """
+        Populate the result table for yara scanning.
+        Called everytime rules are loaded / scanned.
+        """
+        self.rules_table.clear()
+        self.rules_table.setSortingEnabled(False)
+
+        rule_results = self.ys.getResults()
+
+        self._setRuleTableHeaderLabels()
+        table_data = self._calculateRuleTableData(rule_results)
+        row_count = len(table_data)
+
+        self.rules_table.setColumnCount(len(self.rules_table_header_labels))
+        self.rules_table.setHorizontalHeaderLabels(self.rules_table_header_labels)
+        self.rules_table.setRowCount(row_count)
+        self.rules_table.resizeRowToContents(0)
+
+        for row, data_item in enumerate(table_data):
+            for column, column_name in enumerate(self.rules_table_header_labels):
+                tmp_item = self._getRuleTableItem(data_item, column)
+                tmp_item.setFlags(tmp_item.flags() & ~self.QtCore.Qt.ItemIsEditable)
+                tmp_item.setTextAlignment(self.QtCore.Qt.AlignRight)
+                self.rules_table.setItem(row, column, tmp_item)
+            self.rules_table.resizeRowToContents(row)
+
+        self.rules_table.setSelectionMode(self.QtGui.QAbstractItemView.SingleSelection)
+        self.rules_table.resizeColumnsToContents()
+        self.rules_table.setSortingEnabled(True)
+
+        if len(rule_results) > 0:
+            self.populateResultTable(rule_results[0])
+
+    def _calculateRuleTableData(self, rule_results):
+        """
+        Prepare data for display in the result table for yara scan.
+        @param rule_results: results of matching as performed by Yarascanner
+        @type: rule_results: a list of dict, as generated by Yara
+        @return: (a list of elements) where elements are temporary dictionaries prepared for display
+        """
+        result = []
+        for rule in rule_results:
+            matched_ids = [item[1] for item in rule.match_data["strings"]]
+            num_unique_strings = len(set(matched_ids))
+            result.append({"name": rule.rule_name,
+                           "num_matched_strings": num_unique_strings,
+                           "num_all_strings": len(rule.strings),
+                           "match": "%s" % rule.match_data["matches"]})
+        return result
+
+    def _setRuleTableHeaderLabels(self):
+        """
+        Set the header labels for the yara scan result table.
+        """
+        self.rules_table_header_labels = ["Rule Name", "Strings Matched", "% Matched", "Match?"]
+
+    def _getRuleTableItem(self, data_item, column_index):
+        """
+        Transform a data item for display in the result table
+        @param data_item: the item to prepare for display
+        @type data_item: a dictionary containing rule results
+        @param column_index: the column to prepare the item for
+        @type column_index: int
+        @return: the prepared item
+        """
+        tmp_item = self.QtGui.QTableWidgetItem()
+        if column_index == 0:
+            tmp_item = self.QtGui.QTableWidgetItem(data_item["name"])
+        elif column_index == 1:
+            tmp_item = self.NumberQTableWidgetItem("%d" % data_item["num_matched_strings"])
+        elif column_index == 2:
+            if data_item["num_all_strings"] > 0:
+                percentage = 100.0 * data_item["num_matched_strings"] / data_item["num_all_strings"]
+                tmp_item = self.NumberQTableWidgetItem("%3.2f" % percentage)
+            else:
+                tmp_item = self.NumberQTableWidgetItem("0")
+        elif column_index == 3:
+            tmp_item = self.QtGui.QTableWidgetItem(data_item["match"])
+        if data_item["match"] == "True":
+            tmp_item.setBackground(self.QtGui.QBrush(self.QtGui.QColor(0xCC0000)))
+        elif data_item["match"] == "False" and data_item["num_matched_strings"] > 0:
+            tmp_item.setBackground(self.QtGui.QBrush(self.QtGui.QColor(0xFFBB00)))
+        else:
+            tmp_item.setBackground(self.QtGui.QBrush(self.QtGui.QColor(0x22CC00)))
+        return tmp_item
+
+    def _onRuleClicked(self, mi):
+        """
+        The action to perform when an entry in the arithmetic/logic table is double clicked.
+        Changes IDA View either to the function or basic block, depending on the column clicked.
+        """
+        clicked_rule_name = self.rules_table.item(mi.row(), 0).text()
+        for rule_result in self.ys.getResults():
+            if rule_result.rule_name == clicked_rule_name:
+                self.populateResultTable(rule_result)
+                self._selected_rule = rule_result
+
+################################################################################
+# Detailed Result GUI
+################################################################################
+
+    def _createResultWidget(self):
+        """
+        Create the widget for the arithmetic/logic heuristic.
+        """
+        self.result_widget = QtGui.QWidget()
+        result_layout = QtGui.QVBoxLayout()
+        num_hits = 0
+        num_strings = 0
+        self.result_label = QtGui.QLabel("%d out of %d strings matched" % (num_hits, num_strings))
+        self.rule_display_icon = QIcon(self.parent.config.icon_file_path + "winapi.png")
+
+        # rule visualization
+        self.result_widget = QtGui.QWidget()
+        result_layout = QtGui.QVBoxLayout()
+        self._createResultTable()
+        self._createResultInfoButton()
+
+        self.result_info_widget = QtGui.QWidget()
+        result_info_layout = QtGui.QHBoxLayout()
+        result_info_layout.addWidget(self.result_info_button)
+        result_info_layout.addWidget(self.result_label)
+        result_info_layout.addStretch(1)
+        self.result_info_widget.setLayout(result_info_layout)
+
+        # widget composition
+        result_layout.addWidget(self.result_info_widget)
+        result_layout.addWidget(self.result_table)
+        self.result_widget.setLayout(result_layout)
+
+    def _createResultInfoButton(self):
+        """
+        Create a back button to allow easier browsing
+        """
+        self.result_info_button = QtGui.QPushButton(self.rule_display_icon, "", self)
+        self.result_info_button.setToolTip("Show full rule")
+        self.result_info_button.resize(self.result_info_button.sizeHint())
+        self.result_info_button.setEnabled(True)
+        self.result_info_button.clicked.connect(self._onResultInfoButtonClicked)
+
+    def _createResultTable(self):
+        """
+        Create the result table for displaying results yara scanning
+        """
+        self.result_table = QtGui.QTableWidget()
+        self.populateResultTable(None)
+        self.result_table.doubleClicked.connect(self._onResultDoubleClicked)
+
+    def populateResultTable(self, rule_result):
+        """
+        Populate the result table for yara scanning.
+        Called everytime rules are loaded / scanned.
+        """
+        self.result_table.clear()
+        self.result_table.setSortingEnabled(False)
+
+        self._setResultTableHeaderLabels()
+        table_data = self._calculateResultTableData(rule_result)
+        row_count = len(table_data)
+        matched_str = set([])
+        all_str = set([])
+        for data_item in table_data:
+            if data_item[0]:
+                matched_str.update([data_item[2]])
+            all_str.update([data_item[2]])
+        rule_label = ""
+        if rule_result:
+            rule_label = "%d out of %d strings matched (%s)" % (len(matched_str), len(all_str), rule_result.rule_name)
+        else:
+            rule_label = "No rule selected."
+        self.result_label.setText(rule_label)
+
+        self.result_table.setColumnCount(len(self.result_table_header_labels))
+        self.result_table.setHorizontalHeaderLabels(self.result_table_header_labels)
+        self.result_table.setRowCount(row_count)
+        self.result_table.resizeRowToContents(0)
+
+        for row, data_item in enumerate(table_data):
+            for column, column_name in enumerate(self.result_table_header_labels):
+                tmp_item = self._getResultTableItem(data_item, column)
+                tmp_item.setFlags(tmp_item.flags() & ~self.QtCore.Qt.ItemIsEditable)
+                tmp_item.setTextAlignment(self.QtCore.Qt.AlignRight)
+                self.result_table.setItem(row, column, tmp_item)
+            self.result_table.resizeRowToContents(row)
+
+        self.result_table.setSelectionMode(self.QtGui.QAbstractItemView.SingleSelection)
+        self.result_table.resizeColumnsToContents()
+        self.result_table.setSortingEnabled(True)
+
+    def _setResultTableHeaderLabels(self):
+        """
+        Set the header labels for the yara scan result table.
+        """
+        self.result_table_header_labels = ["Address / Type", "String ID", "Value"]
+
+    def _calculateResultTableData(self, rule_result):
+        """
+        Prepare data for display in the result table for yara scan.
+        @param rule_results: results of matching as performed by Yarascanner
+        @type: rule_results: a list of dict, as generated by Yara
+        @return: (a list of elements) where elements are temporary dictionaries prepared for display
+        """
+        if not rule_result:
+            return []
+        result = []
+        for string in rule_result.match_data["strings"]:
+            res_tuple = (True, string[0], string[1], string[2])
+            result.append(res_tuple)
+        matched_strings = [string[2] for string in result]
+        for string in rule_result.strings:
+            if string[1] not in matched_strings:
+                res_tuple = (False, string[0], string[1], string[2])
+                result.append(res_tuple)
+        return result
+
+    def _getResultTableItem(self, data_item, column_index):
+        """
+        Transform a data item for display in the result table
+        @param data_item: the item to prepare for display
+        @type data_item: a dictionary containing rule results
+        @param column_index: the column to prepare the item for
+        @type column_index: int
+        @return: the prepared item
+        """
+        tmp_item = self.QtGui.QTableWidgetItem()
+        if column_index == 0:
+            if data_item[0]:
+                tmp_item = self.QtGui.QTableWidgetItem("0x%x" % data_item[1])
+            else:
+                tmp_item = self.QtGui.QTableWidgetItem("%s" % data_item[1])
+        elif column_index == 1:
+            tmp_item = self.QtGui.QTableWidgetItem("%s" % data_item[2])
+        elif column_index == 2:
+            tmp_item = self.QtGui.QTableWidgetItem("%s" % data_item[3])
+        if data_item[0]:
+            tmp_item.setBackground(self.QtGui.QBrush(self.QtGui.QColor(0xCC0000)))
+        else:
+            tmp_item.setBackground(self.QtGui.QBrush(self.QtGui.QColor(0x22CC00)))
+        return tmp_item
+
+    def _onResultDoubleClicked(self, mi):
+        """
+        The action to perform when an entry in the arithmetic/logic table is double clicked.
+        Changes IDA View either to the function or basic block, depending on the column clicked.
+        """
+        clicked_address = self.result_table.item(mi.row(), 0).text()
+        try:
+            self.ip.Jump(int(clicked_address, 16))
+        except ValueError:
+            pass
+
+    def _onResultInfoButtonClicked(self):
+        dialog = self.YaraRuleDialog(self._selected_rule)
+        dialog.exec_()