mirror of
https://github.com/mandiant/capa.git
synced 2026-02-04 11:07:53 -08:00
Merge branch 'master' of github.com:fireeye/capa into fix-703
This commit is contained in:
69
CHANGELOG.md
69
CHANGELOG.md
@@ -2,8 +2,66 @@
|
||||
|
||||
## master (unreleased)
|
||||
|
||||
The first Python 3 ONLY capa version.
|
||||
It includes many new rules, including all new techniques introduced in MITRE ATT&CK v9.
|
||||
### New Features
|
||||
|
||||
- explorer: allow user to add specified number of bytes when adding a Bytes feature in the Rule Generator #689 @mike-hunhoff
|
||||
- explorer: enforce max column width Features and Editor panes #691 @mike-hunhoff
|
||||
- explorer: add option to limit features to currently selected disassembly address #692 @mike-hunhoff
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
### New Rules (20)
|
||||
|
||||
- collection/webcam/capture-webcam-image johnk3r
|
||||
- nursery/list-drag-and-drop-files michael.hunhoff@fireeye.com
|
||||
- nursery/monitor-clipboard-content michael.hunhoff@fireeye.com
|
||||
- nursery/monitor-local-ipv4-address-changes michael.hunhoff@fireeye.com
|
||||
- nursery/load-windows-common-language-runtime michael.hunhoff@fireeye.com
|
||||
- nursery/resize-volume-shadow-copy-storage michael.hunhoff@fireeye.com
|
||||
- nursery/add-user-account-group michael.hunhoff@fireeye.com
|
||||
- nursery/add-user-account-to-group michael.hunhoff@fireeye.com
|
||||
- nursery/add-user-account michael.hunhoff@fireeye.com
|
||||
- nursery/change-user-account-password michael.hunhoff@fireeye.com
|
||||
- nursery/delete-user-account-from-group michael.hunhoff@fireeye.com
|
||||
- nursery/delete-user-account-group michael.hunhoff@fireeye.com
|
||||
- nursery/delete-user-account michael.hunhoff@fireeye.com
|
||||
- nursery/list-domain-servers michael.hunhoff@fireeye.com
|
||||
- nursery/list-groups-for-user-account michael.hunhoff@fireeye.com
|
||||
- nursery/list-user-account-groups michael.hunhoff@fireeye.com
|
||||
- nursery/list-user-accounts-for-group michael.hunhoff@fireeye.com
|
||||
- nursery/list-user-accounts michael.hunhoff@fireeye.com
|
||||
- nursery/parse-url michael.hunhoff@fireeye.com
|
||||
- nursery/register-raw-input-devices michael.hunhoff@fireeye.com
|
||||
-
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
### capa explorer IDA Pro plugin
|
||||
|
||||
- explorer: add additional filter logic when displaying matches by function #686 @mike-hunhoff
|
||||
- explorer: remove duplicate check when saving file #687 @mike-hunhoff
|
||||
- explorer: update IDA extractor to use non-canon mnemonics #688 @mike-hunhoff
|
||||
|
||||
### Development
|
||||
|
||||
### Raw diffs
|
||||
- [capa v2.0.0...master](https://github.com/fireeye/capa/compare/v2.0.0...master)
|
||||
- [capa-rules v2.0.0...master](https://github.com/fireeye/capa-rules/compare/v2.0.0...master)
|
||||
|
||||
|
||||
## v2.0.0 (2021-07-19)
|
||||
|
||||
We are excited to announce version 2.0! :tada:
|
||||
capa 2.0:
|
||||
- enables anyone to contribute rules more easily
|
||||
- is the first Python 3 ONLY version
|
||||
- provides more concise and relevant result via identification of library functions using FLIRT
|
||||

|
||||
- includes many features and enhancements for the capa explorer IDA plugin
|
||||
- adds 93 new rules, including all new techniques introduced in MITRE ATT&CK v9
|
||||
|
||||
A huge thanks to everyone who submitted issues, provided feedback, and contributed code and rules. Many colleagues across dozens of organizations have volunteered their experience to improve this tool! :heart:
|
||||
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -123,7 +181,6 @@ It includes many new rules, including all new techniques introduced in MITRE ATT
|
||||
- nursery/get-token-privileges michael.hunhoff@fireeye.com
|
||||
- nursery/prompt-user-for-credentials michael.hunhoff@fireeye.com
|
||||
- nursery/spoof-parent-pid michael.hunhoff@fireeye.com
|
||||
-
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -151,9 +208,9 @@ It includes many new rules, including all new techniques introduced in MITRE ATT
|
||||
|
||||
### Raw diffs
|
||||
|
||||
<!-- The diff uses v1.6.1 because master doesn't include v1.6.2 -->
|
||||
- [capa v1.6.1...master](https://github.com/fireeye/capa/compare/v1.6.1...master)
|
||||
- [capa-rules v1.6.1...master](https://github.com/fireeye/capa-rules/compare/v1.6.1...master)
|
||||
<!-- The diff uses v1.6.1 because master doesn't include v1.6.2 and v1.6.3 -->
|
||||
- [capa v1.6.1...v2.0.0](https://github.com/fireeye/capa/compare/v1.6.1...v2.0.0)
|
||||
- [capa-rules v1.6.1...v2.0.0](https://github.com/fireeye/capa-rules/compare/v1.6.1...v2.0.0)
|
||||
|
||||
|
||||
## v1.6.3 (2021-04-29)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://pypi.org/project/flare-capa)
|
||||
[](https://github.com/fireeye/capa/releases)
|
||||
[](https://github.com/fireeye/capa-rules)
|
||||
[](https://github.com/fireeye/capa-rules)
|
||||
[](https://github.com/fireeye/capa/actions?query=workflow%3ACI+event%3Apush+branch%3Amaster)
|
||||
[](https://github.com/fireeye/capa/releases)
|
||||
[](LICENSE.txt)
|
||||
@@ -11,7 +11,9 @@ capa detects capabilities in executable files.
|
||||
You run it against a PE file or shellcode and it tells you what it thinks the program can do.
|
||||
For example, it might suggest that the file is a backdoor, is capable of installing services, or relies on HTTP to communicate.
|
||||
|
||||
Check out the overview in our first [capa blog post](https://www.fireeye.com/blog/threat-research/2020/07/capa-automatically-identify-malware-capabilities.html).
|
||||
Check out:
|
||||
- the overview in our first [capa blog post](https://www.fireeye.com/blog/threat-research/2020/07/capa-automatically-identify-malware-capabilities.html)
|
||||
- the major version 2.0 updates described in our [second blog post](https://www.fireeye.com/blog/threat-research/2021/07/capa-2-better-stronger-faster.html)
|
||||
|
||||
```
|
||||
$ capa.exe suspicious.exe
|
||||
|
||||
@@ -337,7 +337,7 @@ def extract_insn_mnemonic_features(f, bb, insn):
|
||||
bb (IDA BasicBlock)
|
||||
insn (IDA insn_t)
|
||||
"""
|
||||
yield Mnemonic(insn.get_canon_mnem()), insn.ea
|
||||
yield Mnemonic(idc.print_insn_mnem(insn.ea)), insn.ea
|
||||
|
||||
|
||||
def extract_insn_peb_access_characteristic_features(f, bb, insn):
|
||||
|
||||
@@ -32,6 +32,8 @@ def extract_file_export_names(pe, file_path):
|
||||
|
||||
if hasattr(pe, "DIRECTORY_ENTRY_EXPORT"):
|
||||
for export in pe.DIRECTORY_ENTRY_EXPORT.symbols:
|
||||
if not export.name:
|
||||
continue
|
||||
try:
|
||||
name = export.name.partition(b"\x00")[0].decode("ascii")
|
||||
except UnicodeDecodeError:
|
||||
|
||||
@@ -54,8 +54,6 @@ class Options(IntFlag):
|
||||
|
||||
def write_file(path, data):
|
||||
""" """
|
||||
if os.path.exists(path) and 1 != idaapi.ask_yn(1, "The file already exists. Overwrite?"):
|
||||
return
|
||||
with open(path, "wb") as save_file:
|
||||
save_file.write(data)
|
||||
|
||||
@@ -277,6 +275,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
self.view_rulegen_editor = None
|
||||
self.view_rulegen_header_label = None
|
||||
self.view_rulegen_search = None
|
||||
self.view_rulegen_limit_features_by_ea = None
|
||||
self.rulegen_current_function = None
|
||||
self.rulegen_bb_features_cache = {}
|
||||
self.rulegen_func_features_cache = {}
|
||||
@@ -467,6 +466,10 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
label2.setText("Editor")
|
||||
label2.setFont(font)
|
||||
|
||||
self.view_rulegen_limit_features_by_ea = QtWidgets.QCheckBox("Limit features to current dissasembly address")
|
||||
self.view_rulegen_limit_features_by_ea.setChecked(False)
|
||||
self.view_rulegen_limit_features_by_ea.stateChanged.connect(self.slot_checkbox_limit_features_by_ea)
|
||||
|
||||
self.view_rulegen_status_label = QtWidgets.QLabel()
|
||||
self.view_rulegen_status_label.setAlignment(QtCore.Qt.AlignLeft)
|
||||
self.view_rulegen_status_label.setText("")
|
||||
@@ -497,6 +500,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
layout3.addWidget(self.view_rulegen_editor, 65)
|
||||
|
||||
layout2.addWidget(self.view_rulegen_header_label)
|
||||
layout2.addWidget(self.view_rulegen_limit_features_by_ea)
|
||||
layout2.addWidget(self.view_rulegen_search)
|
||||
layout2.addWidget(self.view_rulegen_features)
|
||||
|
||||
@@ -561,6 +565,10 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
self.limit_results_to_function(idaapi.get_func(ea))
|
||||
self.view_tree.reset_ui()
|
||||
|
||||
def update_rulegen_tree_limit_features_to_selection(self, ea):
|
||||
""" """
|
||||
self.view_rulegen_features.filter_items_by_ea(ea)
|
||||
|
||||
def ida_hook_screen_ea_changed(self, widget, new_ea, old_ea):
|
||||
"""function hook for IDA "screen ea changed" action
|
||||
|
||||
@@ -581,6 +589,9 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
if not idaapi.get_func(new_ea):
|
||||
return
|
||||
|
||||
if self.view_tabs.currentIndex() == 1 and self.view_rulegen_limit_features_by_ea.isChecked():
|
||||
return self.update_rulegen_tree_limit_features_to_selection(new_ea)
|
||||
|
||||
if idaapi.get_func(new_ea) == idaapi.get_func(old_ea):
|
||||
# user navigated same function - ignore
|
||||
return
|
||||
@@ -982,6 +993,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
self.view_rulegen_editor.reset_view()
|
||||
self.view_rulegen_preview.reset_view()
|
||||
self.view_rulegen_search.clear()
|
||||
self.view_rulegen_limit_features_by_ea.setChecked(False)
|
||||
self.set_rulegen_preview_border_neutral()
|
||||
self.rulegen_current_function = None
|
||||
self.rulegen_func_features_cache = {}
|
||||
@@ -1140,7 +1152,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
|
||||
s = json.dumps(self.doc, sort_keys=True, cls=capa.render.json.CapaJsonObjectEncoder).encode("utf-8")
|
||||
|
||||
path = idaapi.ask_file(True, "*.json", "Choose file to save capa program analysis JSON")
|
||||
path = self.ask_user_capa_json_file()
|
||||
if not path:
|
||||
return
|
||||
|
||||
@@ -1173,6 +1185,13 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
|
||||
self.view_tree.reset_ui()
|
||||
|
||||
def slot_checkbox_limit_features_by_ea(self, state):
|
||||
""" """
|
||||
if state == QtCore.Qt.Checked:
|
||||
self.view_rulegen_features.filter_items_by_ea(idaapi.get_screen_ea())
|
||||
else:
|
||||
self.view_rulegen_features.show_all_items()
|
||||
|
||||
def slot_checkbox_show_results_by_function_changed(self, state):
|
||||
"""slot activated if checkbox clicked
|
||||
|
||||
@@ -1216,7 +1235,16 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
def ask_user_capa_rule_file(self):
|
||||
""" """
|
||||
return QtWidgets.QFileDialog.getSaveFileName(
|
||||
None, "Please select a capa rule to edit", settings.user.get(CAPA_SETTINGS_RULE_PATH, ""), "*.yml"
|
||||
None,
|
||||
"Please select a location to save capa rule file",
|
||||
settings.user.get(CAPA_SETTINGS_RULE_PATH, ""),
|
||||
"*.yml",
|
||||
)[0]
|
||||
|
||||
def ask_user_capa_json_file(self):
|
||||
""" """
|
||||
return QtWidgets.QFileDialog.getSaveFileName(
|
||||
None, "Please select a location to save capa JSON file", "", "*.json"
|
||||
)[0]
|
||||
|
||||
def set_view_status_label(self, text):
|
||||
|
||||
@@ -435,12 +435,18 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
|
||||
for ea in rule["matches"].keys():
|
||||
ea = capa.ida.helpers.get_func_start_ea(ea)
|
||||
if ea is None:
|
||||
# file scope, skip for rendering in this mode
|
||||
# file scope, skip rendering in this mode
|
||||
continue
|
||||
if None is matches_by_function.get(ea, None):
|
||||
matches_by_function[ea] = CapaExplorerFunctionItem(self.root_node, ea, can_check=False)
|
||||
if not matches_by_function.get(ea, ()):
|
||||
# new function root
|
||||
matches_by_function[ea] = (CapaExplorerFunctionItem(self.root_node, ea, can_check=False), [])
|
||||
function_root, match_cache = matches_by_function[ea]
|
||||
if rule["meta"]["name"] in match_cache:
|
||||
# rule match already rendered for this function root, skip it
|
||||
continue
|
||||
match_cache.append(rule["meta"]["name"])
|
||||
CapaExplorerRuleItem(
|
||||
matches_by_function[ea],
|
||||
function_root,
|
||||
rule["meta"]["name"],
|
||||
rule["meta"].get("namespace"),
|
||||
len(rule["matches"]),
|
||||
|
||||
@@ -9,6 +9,7 @@ import re
|
||||
from collections import Counter
|
||||
|
||||
import idc
|
||||
import idaapi
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
|
||||
import capa.rules
|
||||
@@ -178,6 +179,13 @@ def build_context_menu(o, actions):
|
||||
return menu
|
||||
|
||||
|
||||
def resize_columns_to_content(header):
|
||||
""" """
|
||||
header.resizeSections(QtWidgets.QHeaderView.ResizeToContents)
|
||||
if header.sectionSize(0) > MAX_SECTION_SIZE:
|
||||
header.resizeSection(0, MAX_SECTION_SIZE)
|
||||
|
||||
|
||||
class CapaExplorerRulgenPreview(QtWidgets.QTextEdit):
|
||||
|
||||
INDENT = " " * 2
|
||||
@@ -319,7 +327,6 @@ class CapaExplorerRulgenEditor(QtWidgets.QTreeWidget):
|
||||
self.preview = preview
|
||||
|
||||
self.setHeaderLabels(["Feature", "Description", "Comment"])
|
||||
self.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
|
||||
self.header().setStretchLastSection(False)
|
||||
self.setExpandsOnDoubleClick(False)
|
||||
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
||||
@@ -327,6 +334,10 @@ class CapaExplorerRulgenEditor(QtWidgets.QTreeWidget):
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self.setStyleSheet("QTreeView::item {padding-right: 15 px;padding-bottom: 2 px;}")
|
||||
|
||||
# configure view columns to auto-resize
|
||||
for idx in range(3):
|
||||
self.header().setSectionResizeMode(idx, QtWidgets.QHeaderView.Interactive)
|
||||
|
||||
# enable drag and drop
|
||||
self.setDragEnabled(True)
|
||||
self.setAcceptDrops(True)
|
||||
@@ -336,6 +347,8 @@ class CapaExplorerRulgenEditor(QtWidgets.QTreeWidget):
|
||||
self.itemChanged.connect(self.slot_item_changed)
|
||||
self.customContextMenuRequested.connect(self.slot_custom_context_menu_requested)
|
||||
self.itemDoubleClicked.connect(self.slot_item_double_clicked)
|
||||
self.expanded.connect(self.slot_resize_columns_to_content)
|
||||
self.collapsed.connect(self.slot_resize_columns_to_content)
|
||||
|
||||
self.root = None
|
||||
self.reset_view()
|
||||
@@ -396,6 +409,10 @@ class CapaExplorerRulgenEditor(QtWidgets.QTreeWidget):
|
||||
self.root = None
|
||||
self.clear()
|
||||
|
||||
def slot_resize_columns_to_content(self):
|
||||
""" """
|
||||
resize_columns_to_content(self.header())
|
||||
|
||||
def slot_item_changed(self, item, column):
|
||||
""" """
|
||||
if self.is_editing:
|
||||
@@ -645,6 +662,7 @@ class CapaExplorerRulgenEditor(QtWidgets.QTreeWidget):
|
||||
|
||||
self.expandAll()
|
||||
self.update_preview()
|
||||
resize_columns_to_content(self.header())
|
||||
|
||||
def load_features_from_yaml(self, rule_text, update_preview=False):
|
||||
""" """
|
||||
@@ -736,9 +754,12 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget):
|
||||
self.editor = editor
|
||||
|
||||
self.setHeaderLabels(["Feature", "Virtual Address"])
|
||||
self.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
|
||||
self.setStyleSheet("QTreeView::item {padding-right: 15 px;padding-bottom: 2 px;}")
|
||||
|
||||
# configure view columns to auto-resize
|
||||
for idx in range(2):
|
||||
self.header().setSectionResizeMode(idx, QtWidgets.QHeaderView.Interactive)
|
||||
|
||||
self.setExpandsOnDoubleClick(False)
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
@@ -746,6 +767,8 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget):
|
||||
# connect slots
|
||||
self.itemDoubleClicked.connect(self.slot_item_double_clicked)
|
||||
self.customContextMenuRequested.connect(self.slot_custom_context_menu_requested)
|
||||
self.expanded.connect(self.slot_resize_columns_to_content)
|
||||
self.collapsed.connect(self.slot_resize_columns_to_content)
|
||||
|
||||
self.reset_view()
|
||||
|
||||
@@ -773,12 +796,24 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget):
|
||||
""" """
|
||||
self.clear()
|
||||
|
||||
def slot_resize_columns_to_content(self):
|
||||
""" """
|
||||
resize_columns_to_content(self.header())
|
||||
|
||||
def slot_add_selected_features(self, action):
|
||||
""" """
|
||||
selected = [item.data(0, 0x100) for item in self.selectedItems()]
|
||||
if selected:
|
||||
self.editor.update_features(selected)
|
||||
|
||||
def slot_add_n_bytes_feature(self, action):
|
||||
""" """
|
||||
count = idaapi.ask_long(16, f"Enter number of bytes (1-{capa.features.common.MAX_BYTES_FEATURE_SIZE}):")
|
||||
if count and 1 <= count <= capa.features.common.MAX_BYTES_FEATURE_SIZE:
|
||||
item = self.selectedItems()[0].data(0, 0x100)
|
||||
item.value = item.value[:count]
|
||||
self.editor.update_features([item])
|
||||
|
||||
def slot_custom_context_menu_requested(self, pos):
|
||||
""" """
|
||||
actions = []
|
||||
@@ -790,6 +825,8 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget):
|
||||
|
||||
if selected_items_count == 1:
|
||||
action_add_features_fmt = "Add feature"
|
||||
if isinstance(self.selectedItems()[0].data(0, 0x100), capa.features.common.Bytes):
|
||||
actions.append(("Add n bytes...", (), self.slot_add_n_bytes_feature))
|
||||
else:
|
||||
action_add_features_fmt = "Add %d features" % selected_items_count
|
||||
|
||||
@@ -826,6 +863,44 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget):
|
||||
else:
|
||||
self.show_all_items()
|
||||
|
||||
def filter_items_by_ea(self, min_ea, max_ea=None):
|
||||
""" """
|
||||
visited = []
|
||||
|
||||
def show_item_and_parents(_o):
|
||||
"""iteratively show and expand an item and its' parents"""
|
||||
while _o:
|
||||
visited.append(_o)
|
||||
_o.setHidden(False)
|
||||
_o.setExpanded(True)
|
||||
_o = _o.parent()
|
||||
|
||||
for o in iterate_tree(self):
|
||||
if o in visited:
|
||||
# save some cycles, only visit item once
|
||||
continue
|
||||
|
||||
# read ea from "Address" column
|
||||
o_ea = o.text(CapaExplorerRulegenFeatures.get_column_address_index())
|
||||
|
||||
if o_ea == "":
|
||||
# ea may be empty, hide by default
|
||||
o.setHidden(True)
|
||||
continue
|
||||
|
||||
o_ea = int(o_ea, 16)
|
||||
|
||||
if max_ea is not None and min_ea <= o_ea <= max_ea:
|
||||
show_item_and_parents(o)
|
||||
elif o_ea == min_ea:
|
||||
show_item_and_parents(o)
|
||||
else:
|
||||
# made it here, hide by default
|
||||
o.setHidden(True)
|
||||
|
||||
# resize the view for UX
|
||||
resize_columns_to_content(self.header())
|
||||
|
||||
def style_parent_node(self, o):
|
||||
""" """
|
||||
font = QtGui.QFont()
|
||||
@@ -887,6 +962,7 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget):
|
||||
self.parse_features_for_tree(self.new_parent_node(self, ("File Scope",)), file_features)
|
||||
if func_features:
|
||||
self.parse_features_for_tree(self.new_parent_node(self, ("Function/Basic Block Scope",)), func_features)
|
||||
resize_columns_to_content(self.header())
|
||||
|
||||
def parse_features_for_tree(self, parent, features):
|
||||
""" """
|
||||
@@ -1000,11 +1076,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
|
||||
def slot_resize_columns_to_content(self):
|
||||
"""reset view columns to contents"""
|
||||
if self.should_resize_columns:
|
||||
self.header().resizeSections(QtWidgets.QHeaderView.ResizeToContents)
|
||||
|
||||
# limit size of first section
|
||||
if self.header().sectionSize(0) > MAX_SECTION_SIZE:
|
||||
self.header().resizeSection(0, MAX_SECTION_SIZE)
|
||||
resize_columns_to_content(self.header())
|
||||
|
||||
def map_index_to_source_item(self, model_index):
|
||||
"""map proxy model index to source model item
|
||||
|
||||
25
capa/main.py
25
capa/main.py
@@ -330,7 +330,11 @@ def register_flirt_signature_analyzers(vw, sigpaths):
|
||||
import viv_utils.flirt
|
||||
|
||||
for sigpath in sigpaths:
|
||||
sigs = load_flirt_signature(sigpath)
|
||||
try:
|
||||
sigs = load_flirt_signature(sigpath)
|
||||
except ValueError as e:
|
||||
logger.warning("could not load %s: %s", sigpath, str(e))
|
||||
continue
|
||||
|
||||
logger.debug("flirt: sig count: %d", len(sigs))
|
||||
|
||||
@@ -550,14 +554,23 @@ def get_signatures(sigs_path):
|
||||
if os.path.isfile(sigs_path):
|
||||
paths.append(sigs_path)
|
||||
elif os.path.isdir(sigs_path):
|
||||
logger.debug("reading signatures from directory %s", sigs_path)
|
||||
logger.debug("reading signatures from directory %s", os.path.abspath(os.path.normpath(sigs_path)))
|
||||
for root, dirs, files in os.walk(sigs_path):
|
||||
for file in files:
|
||||
if file.endswith((".pat", ".pat.gz", ".sig")):
|
||||
sig_path = os.path.join(root, file)
|
||||
logger.debug("found signature: %s", sig_path)
|
||||
paths.append(sig_path)
|
||||
|
||||
# nicely normalize and format path so that debugging messages are clearer
|
||||
paths = [os.path.abspath(os.path.normpath(path)) for path in paths]
|
||||
|
||||
# load signatures in deterministic order: the alphabetic sorting of filename.
|
||||
# this means that `0_sigs.pat` loads before `1_sigs.pat`.
|
||||
paths = sorted(paths, key=os.path.basename)
|
||||
|
||||
for path in paths:
|
||||
logger.debug("found signature file: %s", path)
|
||||
|
||||
return paths
|
||||
|
||||
|
||||
@@ -691,11 +704,11 @@ def install_common_args(parser, wanted=None):
|
||||
|
||||
if "signatures" in wanted:
|
||||
parser.add_argument(
|
||||
"--signature",
|
||||
dest="signatures",
|
||||
"-s",
|
||||
"--signatures",
|
||||
type=str,
|
||||
default=SIGNATURES_PATH_DEFAULT_STRING,
|
||||
help="use the given signatures to identify library functions, file system paths to .sig/.pat files.",
|
||||
help="path to .sig/.pat file or directory used to identify library functions, use embedded signatures by default",
|
||||
)
|
||||
|
||||
if "tag" in wanted:
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "1.6.1"
|
||||
__version__ = "2.0.0"
|
||||
|
||||
BIN
doc/img/changelog/flirt-ignore.png
Normal file
BIN
doc/img/changelog/flirt-ignore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
@@ -27,6 +27,10 @@ To install capa as a Python library use `pip` to fetch the `flare-capa` module.
|
||||
#### *Note*:
|
||||
This method is appropriate for integrating capa in an existing project.
|
||||
This technique doesn't pull the default rule set, so you should check it out separately from [capa-rules](https://github.com/fireeye/capa-rules/) and pass the directory to the entrypoint using `-r` or set the rules path in the IDA Pro plugin.
|
||||
This technique also doesn't set up the default library identification [signatures](https://github.com/fireeye/capa/tree/master/sigs). You can pass the signature directory using the `-s` argument.
|
||||
For example, to run capa with both a rule path and a signature path:
|
||||
|
||||
capa -r /path/to/capa-rules -s /path/to/capa-sigs suspicious.exe
|
||||
Alternatively, see Method 3 below.
|
||||
|
||||
### 1. Install capa module
|
||||
|
||||
2
rules
2
rules
Submodule rules updated: a207fbba0a...77d77601b1
16
setup.py
16
setup.py
@@ -11,7 +11,7 @@ import os
|
||||
import setuptools
|
||||
|
||||
requirements = [
|
||||
"tqdm==4.61.1",
|
||||
"tqdm==4.62.0",
|
||||
"pyyaml==5.4.1",
|
||||
"tabulate==0.8.9",
|
||||
"colorama==0.4.4",
|
||||
@@ -23,7 +23,7 @@ requirements = [
|
||||
"networkx==2.5.1",
|
||||
"ruamel.yaml==0.17.10",
|
||||
"vivisect==1.0.3",
|
||||
"smda==1.5.18",
|
||||
"smda==1.5.19",
|
||||
"pefile==2021.5.24",
|
||||
"typing==3.7.4.3",
|
||||
]
|
||||
@@ -71,15 +71,15 @@ setuptools.setup(
|
||||
"pytest-instafail==0.4.2",
|
||||
"pytest-cov==2.12.1",
|
||||
"pycodestyle==2.7.0",
|
||||
"black==21.6b0",
|
||||
"isort==5.9.1",
|
||||
"black==21.7b0",
|
||||
"isort==5.9.3",
|
||||
"mypy==0.910",
|
||||
# type stubs for mypy
|
||||
"types-backports==0.1.3",
|
||||
"types-colorama==0.4.2",
|
||||
"types-PyYAML==5.4.3",
|
||||
"types-tabulate==0.1.1",
|
||||
"types-termcolor==0.1.1",
|
||||
"types-colorama==0.4.3",
|
||||
"types-PyYAML==5.4.6",
|
||||
"types-tabulate==0.8.2",
|
||||
"types-termcolor==1.1.1",
|
||||
],
|
||||
},
|
||||
zip_safe=False,
|
||||
|
||||
@@ -4,6 +4,6 @@ This directory contains FLIRT signatures that capa uses to identify library func
|
||||
Typically, capa will ignore library functions, which reduces false positives and improves runtime.
|
||||
|
||||
These FLIRT signatures were generated by FireEye using the Hex-Rays FLAIR tools such as `pcf` and `sigmake`.
|
||||
FireEye generated the signatures from source data that they collected; these signatures are not derived from the FLIRT signatures distributed with IDA PRo.
|
||||
FireEye generated the signatures from source data that they collected; these signatures are not derived from the FLIRT signatures distributed with IDA Pro.
|
||||
|
||||
The signatures in this directory have the same license as capa: Apache 2.0.
|
||||
The signatures in this directory have the same license as capa: Apache 2.0.
|
||||
|
||||
Submodule tests/data updated: afb5249689...970a89264d
@@ -73,9 +73,9 @@ def get_viv_extractor(path):
|
||||
sigpaths = [
|
||||
os.path.join(CD, "data", "sigs", "test_aulldiv.pat"),
|
||||
os.path.join(CD, "data", "sigs", "test_aullrem.pat.gz"),
|
||||
os.path.join(CD, "..", "sigs", "flare_common_libs.sig"),
|
||||
os.path.join(CD, "..", "sigs", "flare_msvc_atlmfc_32_64.sig"),
|
||||
os.path.join(CD, "..", "sigs", "flare_msvc_rtf_32_64.sig"),
|
||||
os.path.join(CD, "..", "sigs", "1_flare_msvc_rtf_32_64.sig"),
|
||||
os.path.join(CD, "..", "sigs", "2_flare_msvc_atlmfc_32_64.sig"),
|
||||
os.path.join(CD, "..", "sigs", "3_flare_common_libs.sig"),
|
||||
]
|
||||
|
||||
if "raw32" in path:
|
||||
|
||||
Reference in New Issue
Block a user