diff --git a/LICENSE.txt b/LICENSE.txt index d6456956..47e450fe 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright (C) 2020 FireEye, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 11ee0337..25abd9af 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![capa](.github/logo.png) [![CI status](https://github.com/fireeye/capa/workflows/CI/badge.svg)](https://github.com/fireeye/capa/actions?query=workflow%3ACI+event%3Apush+branch%3Amaster) -[![Number of rules](https://img.shields.io/badge/rules-267-blue.svg)](https://github.com/fireeye/capa-rules) +[![Number of rules](https://img.shields.io/badge/rules-266-blue.svg)](https://github.com/fireeye/capa-rules) [![License](https://img.shields.io/badge/license-Apache--2.0-green.svg)](LICENSE.txt) capa detects capabilities in executable files. diff --git a/doc/installation.md b/doc/installation.md index 0aff8993..a54525ee 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -47,7 +47,7 @@ To only get the source code and our provided rules (common), follow these steps: - `$ git clone https://github.com/fireeye/capa.git /local/path/to/src` (HTTPS) - `$ git clone git@github.com:fireeye/capa.git /local/path/to/src` (SSH) - `$ cd /local/path/to/src` -- `$ git submodule update rules` +- `$ git submodule update --init rules` ### 2. Install the local source code Use `pip` to install the source code in "editable" mode. This means that Python will load the capa module from the local directory rather than copying it to `site-packages` or `dist-packages`. This is good because it is easy to modify files and see the effects reflected immediately. But, be careful not to remove this directory unless uninstalling capa. diff --git a/rules b/rules index b878effe..4c9cb029 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit b878effef7d8c9a98876a29103e3226aea2707cc +Subproject commit 4c9cb0294994f8e20be00eee6c01e219c1087af5 diff --git a/scripts/import-to-bn.py b/scripts/import-to-bn.py new file mode 100644 index 00000000..33952aef --- /dev/null +++ b/scripts/import-to-bn.py @@ -0,0 +1,112 @@ +j""" +Binary Ninja plugin that imports a capa report, +produced via `capa --json /path/to/sample`, +into the current database. + +It will mark up functions with their capa matches, like: + + ; capa: print debug messages (host-interaction/log/debug/write-event) + ; capa: delete service (host-interaction/service/delete) + ; Attributes: bp-based frame + + public UninstallService + UninstallService proc near + ... + +To use, invoke from the Binary Ninja Tools menu, or from the +command-palette. + +Adapted for Binary Ninja by @psifertex + +This script will verify that the report matches the workspace. +Check the log window for any errors, and/or the summary of changes. + +Derived from: https://github.com/fireeye/capa/blob/master/scripts/import-to-ida.py +""" +import os +import json + +from binaryninja import * + + +def append_func_cmt(bv, va, cmt): + """ + add the given comment to the given function, + if it doesn't already exist. + """ + func = bv.get_function_at(va) + if not func: + raise ValueError("not a function") + + if cmt in func.comment: + return + + func.comment = func.comment + "\n" + cmt + + +def load_analysis(bv): + shortname = os.path.splitext(os.path.basename(bv.file.filename))[0] + dirname = os.path.dirname(bv.file.filename) + log_info(f'dirname: {dirname}\nshortname: {shortname}\n') + if os.access(os.path.join(dirname, shortname + ".js"), os.R_OK): + path = os.path.join(dirname, shortname + ".js") + elif os.access(os.path.join(dirname, shortname + ".json"), os.R_OK): + path = os.path.join(dirname, shortname + ".json") + else: + path = interaction.get_open_filename_input("capa report:", "JSON (*.js *.json);;All Files (*)") + if not path or not os.access(path, os.R_OK): + log_error("Invalid filename.") + return 0 + log_info("Using capa file %s" % path) + + with open(path, "rb") as f: + doc = json.loads(f.read().decode("utf-8")) + + if "meta" not in doc or "rules" not in doc: + log_error("doesn't appear to be a capa report") + return -1 + + a = doc["meta"]["sample"]["md5"].lower() + md5=Transform['MD5'] + rawhex=Transform['RawHex'] + b = rawhex.encode(md5.encode(bv.parent_view.read(bv.parent_view.start, bv.parent_view.end))).decode("utf-8") + if not a == b: + log_error("sample mismatch") + return -2 + + rows = [] + for rule in doc["rules"].values(): + if rule["meta"].get("lib"): + continue + if rule["meta"].get("capa/subscope"): + continue + if rule["meta"]["scope"] != "function": + continue + + name = rule["meta"]["name"] + ns = rule["meta"].get("namespace", "") + for va in rule["matches"].keys(): + va = int(va) + rows.append((ns, name, va)) + + # order by (namespace, name) so that like things show up together + rows = sorted(rows) + for ns, name, va in rows: + if ns: + cmt = "%s (%s)" % (name, ns) + else: + cmt = "%s" % (name,) + + log_info("0x%x: %s" % (va, cmt)) + try: + # message will look something like: + # + # capa: delete service (host-interaction/service/delete) + append_func_cmt(bv, va, "capa: " + cmt) + except ValueError: + continue + + log_info("ok") + + +PluginCommand.register("Load capa file", "Loads an analysis file from capa", load_analysis)