1. Daniel Plohmann
  2. simpliFiRE.IDAscope

Commits

Daniel Plohmann  committed 26986a3

added custom rule loader to display non-matched strings, progress on visualization

  • Participants
  • Parent commits aa03bbd
  • Branches master

Comments (0)

Files changed (6)

File IDAscope.py

View file
         time_before = time.time()
         print ("[/] setting up shared modules...")
         # FIXME: revert commenting
-        self.documentation_helper = DocumentationHelper(self.config)
-        self.semantic_identifier = SemanticIdentifier(self.config)
-        self.winapi_provider = WinApiProvider(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))
         time_before = time.time()
         print ("[/] setting up widgets...")
         # FIXME: revert commenting
-        self.idascope_widgets.append(FunctionInspectionWidget(self))
-        self.idascope_widgets.append(WinApiWidget(self))
-        self.idascope_widgets.append(CryptoIdentificationWidget(self))
+        # 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/core/YaraScanner.py

View file
 #  <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
 
 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():
         self.os = os
         self.re = re
         self.time = time
-        self.ida_proxy = IdaProxy()
         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 test(self):
-        self.load_rules()
-        self.scan()
-
     def getResults(self):
         return self._results
 
     def load_rules(self):
         self.num_files_loaded = 0
+        self._compiled_rules = []
         self._yara_rules = []
         for yara_path in self.idascope_config.yara_sig_folders:
-            for dirpath, dirnames, filenames in os.walk(yara_path):
-                for filename in filenames:
-                    filepath = dirpath + os.sep + filename
-                    try:
-                        rules = yara.compile(filepath)
-                        self._yara_rules.append(rules)
-                        if rules:
-                            self.num_files_loaded += 1
-                    except:
-                        print "[!] Could not load yara rules file: %s" % filepath
+            self._load_recursive(yara_path)
+
+    def _load_recursive(self, yara_path):
+        for dirpath, dirnames, filenames in os.walk(yara_path):
+            for filename in filenames:
+                filepath = dirpath + os.sep + filename
+                try:
+                    print "loading rules from file: %s" % filepath
+                    rules = yara.compile(filepath)
+                    self._compiled_rules.append(rules)
+                    self._yara_rules.extend(self.yrl.loadRulesFromFile(filepath))
+                    if rules:
+                        self.num_files_loaded += 1
+                except:
+                    print "[!] Could not load yara rules file: %s" % filepath
+
+    def scan(self):
+        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 = ""
         for string in data["strings"]:
             adjusted_offsets.append((self._translateMemOffsetToVirtualAddress(string[0]), string[1], string[2]))
         data["strings"] = adjusted_offsets
-        self._results.append(data)
         if data["matches"]:
             print "  [+] Yara Match for signature: %s" % data["rule"]
-        yara.CALLBACK_CONTINUE
+        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
             if seg[1] < offset < seg[2]:
                 va_offset = seg[0] + (offset - seg[1])
         return va_offset
-
-    def scan(self):
-        memory, offsets = self._get_memory()
-        self.segment_offsets = offsets
-        self._results = []
-        matches = []
-        print "[!] Performing Yara scan..."
-        for rule in self._yara_rules:
-            matches.append(rule.match(data=memory, callback=self._result_callback))
-        if len(matches) == 0:
-            print "  [-] no matches. :("

File idascope/core/helpers/YaraRule.py

View file
+#!/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()
+        # 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 += 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))
+            result += "\n"
+        result += "    condition:\n"
+        result += " " * 8 + "%s\n" % self.condition
+        result += "}"
+        return result

File idascope/core/helpers/YaraRuleLoader.py

View file
+#!/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)
+
+    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):
+        """ 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.analyze()
+                current_rule = YaraRule()
+
+        # return list of Yara rules
+        return yara_rules

File idascope/core/helpers/YaraStatusController.py

View file
+#!/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/widgets/YaraScannerWidget.py

View file
         self.central_widget = self.QtGui.QWidget()
         self.setCentralWidget(self.central_widget)
         self._createGui()
+        self._selected_rule = None
 
     def _createGui(self):
         """
         """
         result = []
         for rule in rule_results:
-            string_ids = [item[1] for item in rule["strings"]]
-            num_unique_strings = len(set(string_ids))
-            result.append({"name": rule["rule"], "strings": num_unique_strings, "match": "%s" % rule["matches"]})
+            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", "Match Outcome"]
+        self.rules_table_header_labels = ["Rule Name", "Strings Matched", "% Matched", "Match?"]
 
     def _getRuleTableItem(self, data_item, column_index):
         """
         if column_index == 0:
             tmp_item = self.QtGui.QTableWidgetItem(data_item["name"])
         elif column_index == 1:
-            tmp_item = self.NumberQTableWidgetItem("%d" % data_item["strings"])
+            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["strings"] > 0:
+        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)))
         """
         clicked_rule_name = self.rules_table.item(mi.row(), 0).text()
         for rule_result in self.ys.getResults():
-            if rule_result["rule"] == clicked_rule_name:
-                print "rule found", rule_result
+            if rule_result.rule_name == clicked_rule_name:
+                print "rule found", clicked_rule_name
                 self.populateResultTable(rule_result)
+                self._selected_rule = rule_result
 
 ################################################################################
 # Detailed Result GUI
         num_hits = 0
         num_strings = 0
         self.result_label = QtGui.QLabel("%d out of %d strings matched" % (num_hits, num_strings))
+        # self.rule_builder_button = QIcon(self.parent.config.icon_file_path + "forward.png")
 
         # rule visualization
         self.result_widget = QtGui.QWidget()
         Create the result table for displaying results yara scanning
         """
         self.result_table = QtGui.QTableWidget()
-        self.populateResultTable({})
+        self.populateResultTable(None)
         self.result_table.doubleClicked.connect(self._onResultDoubleClicked)
 
     def populateResultTable(self, rule_result):
         self._setResultTableHeaderLabels()
         table_data = self._calculateResultTableData(rule_result)
         row_count = len(table_data)
-        self.result_label.setText("%d out of ? strings matched" % (row_count))
+        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]])
+        self.result_label.setText("%d out of %d strings matched" % (len(matched_str), len(all_str)))
 
         self.result_table.setColumnCount(len(self.result_table_header_labels))
         self.result_table.setHorizontalHeaderLabels(self.result_table_header_labels)
         """
         Set the header labels for the yara scan result table.
         """
-        self.result_table_header_labels = ["Address", "String ID", "Value"]
+        self.result_table_header_labels = ["Address / Type", "String ID", "Value"]
 
     def _calculateResultTableData(self, rule_result):
         """
         @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 "strings" in rule_result:
-            return rule_result["strings"]
-        else:
+        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):
         """
         """
         tmp_item = self.QtGui.QTableWidgetItem()
         if column_index == 0:
-            tmp_item = self.QtGui.QTableWidgetItem("0x%x" % data_item[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[1])
-        elif column_index == 2:
             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 = 0
-        if mi.column() == 0 or mi.column() == 1:
-            clicked_address = self.result_table.item(mi.row(), 0).text()
-        elif mi.column() >= 2:
-            clicked_address = self.result_table.item(mi.row(), 2).text()
-        self.ip.Jump(int(clicked_address, 16))
+        clicked_address = self.result_table.item(mi.row(), 0).text()
+        try:
+            self.ip.Jump(int(clicked_address, 16))
+        except ValueError:
+            pass