black 20.8b1 updates

This commit is contained in:
Moritz Raabe
2020-08-27 11:26:28 +02:00
parent 3e20f0fc71
commit 34e7991081
25 changed files with 624 additions and 497 deletions

View File

@@ -20,7 +20,7 @@ from capa.features.extractors.helpers import MIN_STACKSTRING_LEN
def get_printable_len(op):
""" Return string length if all operand bytes are ascii or utf16-le printable
"""Return string length if all operand bytes are ascii or utf16-le printable
args:
op (IDA op_t)
@@ -62,7 +62,7 @@ def get_printable_len(op):
def is_mov_imm_to_stack(insn):
""" verify instruction moves immediate onto stack
"""verify instruction moves immediate onto stack
args:
insn (IDA insn_t)
@@ -80,7 +80,7 @@ def is_mov_imm_to_stack(insn):
def bb_contains_stackstring(f, bb):
""" check basic block for stackstring indicators
"""check basic block for stackstring indicators
true if basic block contains enough moves of constant bytes to the stack
@@ -98,7 +98,7 @@ def bb_contains_stackstring(f, bb):
def extract_bb_stackstring(f, bb):
""" extract stackstring indicators from basic block
"""extract stackstring indicators from basic block
args:
f (IDA func_t)
@@ -109,7 +109,7 @@ def extract_bb_stackstring(f, bb):
def extract_bb_tight_loop(f, bb):
""" extract tight loop indicators from a basic block
"""extract tight loop indicators from a basic block
args:
f (IDA func_t)
@@ -120,7 +120,7 @@ def extract_bb_tight_loop(f, bb):
def extract_features(f, bb):
""" extract basic block features
"""extract basic block features
args:
f (IDA func_t)

View File

@@ -20,7 +20,7 @@ from capa.features.file import Export, Import, Section
def check_segment_for_pe(seg):
""" check segment for embedded PE
"""check segment for embedded PE
adapted for IDA from:
https://github.com/vivisect/vivisect/blob/7be4037b1cecc4551b397f840405a1fc606f9b53/PE/carve.py#L19
@@ -67,7 +67,7 @@ def check_segment_for_pe(seg):
def extract_file_embedded_pe():
""" extract embedded PE features
"""extract embedded PE features
IDA must load resource sections for this to be complete
- '-R' from console
@@ -85,7 +85,7 @@ def extract_file_export_names():
def extract_file_import_names():
""" extract function imports
"""extract function imports
1. imports by ordinal:
- modulename.#ordinal
@@ -104,7 +104,7 @@ def extract_file_import_names():
def extract_file_section_names():
""" extract section names
"""extract section names
IDA must load resource sections for this to be complete
- '-R' from console
@@ -115,7 +115,7 @@ def extract_file_section_names():
def extract_file_strings():
""" extract ASCII and UTF-16 LE strings
"""extract ASCII and UTF-16 LE strings
IDA must load resource sections for this to be complete
- '-R' from console

View File

@@ -15,7 +15,7 @@ from capa.features.extractors import loops
def extract_function_calls_to(f):
""" extract callers to a function
"""extract callers to a function
args:
f (IDA func_t)
@@ -25,7 +25,7 @@ def extract_function_calls_to(f):
def extract_function_loop(f):
""" extract loop indicators from a function
"""extract loop indicators from a function
args:
f (IDA func_t)
@@ -42,7 +42,7 @@ def extract_function_loop(f):
def extract_recursive_call(f):
""" extract recursive function call
"""extract recursive function call
args:
f (IDA func_t)
@@ -52,7 +52,7 @@ def extract_recursive_call(f):
def extract_features(f):
""" extract function features
"""extract function features
arg:
f (IDA func_t)

View File

@@ -15,7 +15,7 @@ import idautils
def find_byte_sequence(start, end, seq):
""" find byte sequence
"""find byte sequence
args:
start: min virtual address
@@ -29,7 +29,7 @@ def find_byte_sequence(start, end, seq):
def get_functions(start=None, end=None, skip_thunks=False, skip_libs=False):
""" get functions, range optional
"""get functions, range optional
args:
start: min virtual address
@@ -45,7 +45,7 @@ def get_functions(start=None, end=None, skip_thunks=False, skip_libs=False):
def get_segments(skip_header_segments=False):
""" get list of segments (sections) in the binary image
"""get list of segments (sections) in the binary image
args:
skip_header_segments: IDA may load header segments - skip if set
@@ -57,7 +57,7 @@ def get_segments(skip_header_segments=False):
def get_segment_buffer(seg):
""" return bytes stored in a given segment
"""return bytes stored in a given segment
decrease buffer size until IDA is able to read bytes from the segment
"""
@@ -97,7 +97,7 @@ def get_file_imports():
def get_instructions_in_range(start, end):
""" yield instructions in range
"""yield instructions in range
args:
start: virtual address (inclusive)
@@ -183,7 +183,7 @@ def find_string_at(ea, min=4):
def get_op_phrase_info(op):
""" parse phrase features from operand
"""parse phrase features from operand
Pretty much dup of sark's implementation:
https://github.com/tmr232/Sark/blob/master/sark/code/instruction.py#L28-L73
@@ -269,7 +269,7 @@ def is_op_stack_var(ea, index):
def mask_op_val(op):
""" mask value by data type
"""mask value by data type
necessary due to a bug in AMD64
@@ -289,7 +289,7 @@ def mask_op_val(op):
def is_function_recursive(f):
""" check if function is recursive
"""check if function is recursive
args:
f (IDA func_t)
@@ -301,7 +301,7 @@ def is_function_recursive(f):
def is_basic_block_tight_loop(bb):
""" check basic block loops to self
"""check basic block loops to self
true if last instruction in basic block branches to basic block start

View File

@@ -62,7 +62,7 @@ def check_for_api_call(ctx, insn):
def extract_insn_api_features(f, bb, insn):
""" parse instruction API features
"""parse instruction API features
args:
f (IDA func_t)
@@ -78,7 +78,7 @@ def extract_insn_api_features(f, bb, insn):
def extract_insn_number_features(f, bb, insn):
""" parse instruction number features
"""parse instruction number features
args:
f (IDA func_t)
@@ -109,7 +109,7 @@ def extract_insn_number_features(f, bb, insn):
def extract_insn_bytes_features(f, bb, insn):
""" parse referenced byte sequences
"""parse referenced byte sequences
args:
f (IDA func_t)
@@ -127,7 +127,7 @@ def extract_insn_bytes_features(f, bb, insn):
def extract_insn_string_features(f, bb, insn):
""" parse instruction string features
"""parse instruction string features
args:
f (IDA func_t)
@@ -145,7 +145,7 @@ def extract_insn_string_features(f, bb, insn):
def extract_insn_offset_features(f, bb, insn):
""" parse instruction structure offset features
"""parse instruction structure offset features
args:
f (IDA func_t)
@@ -175,7 +175,7 @@ def extract_insn_offset_features(f, bb, insn):
def contains_stack_cookie_keywords(s):
""" check if string contains stack cookie keywords
"""check if string contains stack cookie keywords
Examples:
xor ecx, ebp ; StackCookie
@@ -190,7 +190,7 @@ def contains_stack_cookie_keywords(s):
def bb_stack_cookie_registers(bb):
""" scan basic block for stack cookie operations
"""scan basic block for stack cookie operations
yield registers ids that may have been used for stack cookie operations
@@ -239,7 +239,7 @@ def is_nzxor_stack_cookie(f, bb, insn):
def extract_insn_nzxor_characteristic_features(f, bb, insn):
""" parse instruction non-zeroing XOR instruction
"""parse instruction non-zeroing XOR instruction
ignore expected non-zeroing XORs, e.g. security cookies
@@ -258,7 +258,7 @@ def extract_insn_nzxor_characteristic_features(f, bb, insn):
def extract_insn_mnemonic_features(f, bb, insn):
""" parse instruction mnemonic features
"""parse instruction mnemonic features
args:
f (IDA func_t)
@@ -269,7 +269,7 @@ def extract_insn_mnemonic_features(f, bb, insn):
def extract_insn_peb_access_characteristic_features(f, bb, insn):
""" parse instruction peb access
"""parse instruction peb access
fs:[0x30] on x86, gs:[0x60] on x64
@@ -291,7 +291,7 @@ def extract_insn_peb_access_characteristic_features(f, bb, insn):
def extract_insn_segment_access_features(f, bb, insn):
""" parse instruction fs or gs access
"""parse instruction fs or gs access
TODO:
IDA should be able to do this...
@@ -312,7 +312,7 @@ def extract_insn_segment_access_features(f, bb, insn):
def extract_insn_cross_section_cflow(f, bb, insn):
""" inspect the instruction for a CALL or JMP that crosses section boundaries
"""inspect the instruction for a CALL or JMP that crosses section boundaries
args:
f (IDA func_t)
@@ -332,7 +332,7 @@ def extract_insn_cross_section_cflow(f, bb, insn):
def extract_function_calls_from(f, bb, insn):
""" extract functions calls from features
"""extract functions calls from features
most relevant at the function scope, however, its most efficient to extract at the instruction scope
@@ -347,7 +347,7 @@ def extract_function_calls_from(f, bb, insn):
def extract_function_indirect_call_characteristic_features(f, bb, insn):
""" extract indirect function calls (e.g., call eax or call dword ptr [edx+4])
"""extract indirect function calls (e.g., call eax or call dword ptr [edx+4])
does not include calls like => call ds:dword_ABD4974
most relevant at the function or basic block scope;
@@ -363,7 +363,7 @@ def extract_function_indirect_call_characteristic_features(f, bb, insn):
def extract_features(f, bb, insn):
""" extract instruction features
"""extract instruction features
args:
f (IDA func_t)

View File

@@ -11,7 +11,7 @@ from networkx.algorithms.components import strongly_connected_components
def has_loop(edges, threshold=2):
""" check if a list of edges representing a directed graph contains a loop
"""check if a list of edges representing a directed graph contains a loop
args:
edges: list of edge sets representing a directed graph i.e. [(1, 2), (2, 1)]

View File

@@ -84,7 +84,16 @@ def dumps(extractor):
returns:
str: the serialized features.
"""
ret = {"version": 1, "functions": {}, "scopes": {"file": [], "function": [], "basic block": [], "instruction": [],}}
ret = {
"version": 1,
"functions": {},
"scopes": {
"file": [],
"function": [],
"basic block": [],
"instruction": [],
},
}
for feature, va in extractor.extract_file_features():
ret["scopes"]["file"].append(serialize_feature(feature) + (hex(va), ()))
@@ -99,7 +108,16 @@ def dumps(extractor):
ret["functions"][hex(f)][hex(bb)] = []
for feature, va in extractor.extract_basic_block_features(f, bb):
ret["scopes"]["basic block"].append(serialize_feature(feature) + (hex(va), (hex(f), hex(bb),)))
ret["scopes"]["basic block"].append(
serialize_feature(feature)
+ (
hex(va),
(
hex(f),
hex(bb),
),
)
)
for insnva, insn in sorted(
[(insn.__int__(), insn) for insn in extractor.get_instructions(f, bb)], key=lambda p: p[0]
@@ -108,7 +126,15 @@ def dumps(extractor):
for feature, va in extractor.extract_insn_features(f, bb, insn):
ret["scopes"]["instruction"].append(
serialize_feature(feature) + (hex(va), (hex(f), hex(bb), hex(insnva),))
serialize_feature(feature)
+ (
hex(va),
(
hex(f),
hex(bb),
hex(insnva),
),
)
)
return json.dumps(ret)

View File

@@ -17,7 +17,7 @@ import capa.ida.helpers
def info_to_name(display):
""" extract root value from display name
"""extract root value from display name
e.g. function(my_function) => my_function
"""
@@ -68,14 +68,14 @@ class CapaExplorerDataItem(object):
return self._checked
def appendChild(self, item):
""" add child item
"""add child item
@param item: CapaExplorerDataItem*
"""
self.children.append(item)
def child(self, row):
""" get child row
"""get child row
@param row: TODO
"""

View File

@@ -65,7 +65,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
self.endResetModel()
def columnCount(self, model_index):
""" get the number of columns for the children of the given parent
"""get the number of columns for the children of the given parent
@param model_index: QModelIndex*
@@ -77,7 +77,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
return self.root_node.columnCount()
def data(self, model_index, role):
""" get data stored under the given role for the item referred to by the index
"""get data stored under the given role for the item referred to by the index
@param model_index: QModelIndex*
@param role: QtCore.Qt.*
@@ -151,7 +151,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
return None
def flags(self, model_index):
""" get item flags for given index
"""get item flags for given index
@param model_index: QModelIndex*
@@ -163,7 +163,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
return model_index.internalPointer().flags
def headerData(self, section, orientation, role):
""" get data for the given role and section in the header with the specified orientation
"""get data for the given role and section in the header with the specified orientation
@param section: int
@param orientation: QtCore.Qt.Orientation
@@ -177,7 +177,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
return None
def index(self, row, column, parent):
""" get index of the item in the model specified by the given row, column and parent index
"""get index of the item in the model specified by the given row, column and parent index
@param row: int
@param column: int
@@ -201,7 +201,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
return QtCore.QModelIndex()
def parent(self, model_index):
""" get parent of the model item with the given index
"""get parent of the model item with the given index
if the item has no parent, an invalid QModelIndex* is returned
@@ -221,7 +221,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
return self.createIndex(parent.row(), 0, parent)
def iterateChildrenIndexFromRootIndex(self, model_index, ignore_root=True):
""" depth-first traversal of child nodes
"""depth-first traversal of child nodes
@param model_index: QModelIndex*
@param ignore_root: if set, do not return root index
@@ -248,7 +248,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
stack.append(child_index.child(idx, 0))
def reset_ida_highlighting(self, item, checked):
""" reset IDA highlight for an item
"""reset IDA highlight for an item
@param item: capa explorer item
@param checked: indicates item is or not checked
@@ -275,7 +275,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
idc.set_color(item.location, idc.CIC_ITEM, item.ida_highlight)
def setData(self, model_index, value, role):
""" set the role data for the item at index to value
"""set the role data for the item at index to value
@param model_index: QModelIndex*
@param value: QVariant*
@@ -316,7 +316,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
return False
def rowCount(self, model_index):
""" get the number of rows under the given parent
"""get the number of rows under the given parent
when the parent is valid it means that is returning the number of
children of parent
@@ -336,7 +336,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
return item.childCount()
def render_capa_doc_statement_node(self, parent, statement, locations, doc):
""" render capa statement read from doc
"""render capa statement read from doc
@param parent: parent to which new child is assigned
@param statement: statement read from doc
@@ -383,7 +383,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
raise RuntimeError("unexpected match statement type: " + str(statement))
def render_capa_doc_match(self, parent, match, doc):
""" render capa match read from doc
"""render capa match read from doc
@param parent: parent node to which new child is assigned
@param match: match read from doc
@@ -431,7 +431,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
self.render_capa_doc_match(parent2, child, doc)
def render_capa_doc(self, doc):
""" render capa features specified in doc
"""render capa features specified in doc
@param doc: capa result doc
"""
@@ -457,7 +457,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
self.endResetModel()
def capa_doc_feature_to_display(self, feature):
""" convert capa doc feature type string to display string for ui
"""convert capa doc feature type string to display string for ui
@param feature: capa feature read from doc
@@ -479,7 +479,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
return "%s" % feature["type"]
def render_capa_doc_feature_node(self, parent, feature, locations, doc):
""" process capa doc feature node
"""process capa doc feature node
@param parent: parent node to which child is assigned
@param feature: capa doc feature node
@@ -497,7 +497,13 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
if len(locations) == 1:
# only one location for feature so no need to nest children
parent2 = self.render_capa_doc_feature(parent, feature, next(iter(locations)), doc, display=display,)
parent2 = self.render_capa_doc_feature(
parent,
feature,
next(iter(locations)),
doc,
display=display,
)
else:
# feature has multiple children, nest under one parent feature node
parent2 = CapaExplorerFeatureItem(parent, display)
@@ -508,7 +514,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
return parent2
def render_capa_doc_feature(self, parent, feature, location, doc, display="-"):
""" render capa feature read from doc
"""render capa feature read from doc
@param parent: parent node to which new child is assigned
@param feature: feature read from doc
@@ -575,7 +581,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
raise RuntimeError("unexpected feature type: " + str(feature["type"]))
def update_function_name(self, old_name, new_name):
""" update all instances of old function name with new function name
"""update all instances of old function name with new function name
@param old_name: previous function name
@param new_name: new function name

View File

@@ -17,7 +17,7 @@ class CapaExplorerSortFilterProxyModel(QtCore.QSortFilterProxyModel):
super(CapaExplorerSortFilterProxyModel, self).__init__(parent)
def lessThan(self, left, right):
""" true if the value of the left item is less than value of right item
"""true if the value of the left item is less than value of right item
@param left: QModelIndex*
@param right: QModelIndex*
@@ -40,7 +40,7 @@ class CapaExplorerSortFilterProxyModel(QtCore.QSortFilterProxyModel):
return ldata.lower() < rdata.lower()
def filterAcceptsRow(self, row, parent):
""" true if the item in the row indicated by the given row and parent
"""true if the item in the row indicated by the given row and parent
should be included in the model; otherwise returns false
@param row: int
@@ -63,7 +63,7 @@ class CapaExplorerSortFilterProxyModel(QtCore.QSortFilterProxyModel):
return False
def add_single_string_filter(self, column, string):
""" add fixed string filter
"""add fixed string filter
@param column: key column
@param string: string to sort

View File

@@ -15,7 +15,7 @@ from capa.ida.explorer.model import CapaExplorerDataModel
class CapaExplorerQtreeView(QtWidgets.QTreeView):
""" capa explorer QTreeView implementation
"""capa explorer QTreeView implementation
view controls UI action responses and displays data from
CapaExplorerDataModel
@@ -54,7 +54,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
self.setStyleSheet("QTreeView::item {padding-right: 15 px;padding-bottom: 2 px;}")
def reset(self):
""" reset user interface changes
"""reset user interface changes
called when view should reset any user interface changes
made since the last reset e.g. IDA window highlighting
@@ -67,7 +67,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
self.header().resizeSections(QtWidgets.QHeaderView.ResizeToContents)
def map_index_to_source_item(self, model_index):
""" map proxy model index to source model item
"""map proxy model index to source model item
@param model_index: QModelIndex*
@@ -76,7 +76,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
return self.model.mapToSource(model_index).internalPointer()
def send_data_to_clipboard(self, data):
""" copy data to the clipboard
"""copy data to the clipboard
@param data: data to be copied
"""
@@ -85,7 +85,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
clip.setText(data, mode=clip.Clipboard)
def new_action(self, display, data, slot):
""" create action for context menu
"""create action for context menu
@param display: text displayed to user in context menu
@param data: data passed to slot
@@ -100,7 +100,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
return action
def load_default_context_menu_actions(self, data):
""" yield actions specific to function custom context menu
"""yield actions specific to function custom context menu
@param data: tuple
@@ -116,7 +116,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
yield self.new_action(*action)
def load_function_context_menu_actions(self, data):
""" yield actions specific to function custom context menu
"""yield actions specific to function custom context menu
@param data: tuple
@@ -133,7 +133,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
yield action
def load_default_context_menu(self, pos, item, model_index):
""" create default custom context menu
"""create default custom context menu
creates custom context menu containing default actions
@@ -151,7 +151,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
return menu
def load_function_item_context_menu(self, pos, item, model_index):
""" create function custom context menu
"""create function custom context menu
creates custom context menu containing actions specific to functions
and the default actions
@@ -170,7 +170,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
return menu
def show_custom_context_menu(self, menu, pos):
""" display custom context menu in view
"""display custom context menu in view
@param menu: TODO
@param pos: TODO
@@ -179,7 +179,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
menu.exec_(self.viewport().mapToGlobal(pos))
def slot_copy_column(self, action):
""" slot connected to custom context menu
"""slot connected to custom context menu
allows user to select a column and copy the data
to clipboard
@@ -190,7 +190,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
self.send_data_to_clipboard(item.data(model_index.column()))
def slot_copy_row(self, action):
""" slot connected to custom context menu
"""slot connected to custom context menu
allows user to select a row and copy the space-delimited
data to clipboard
@@ -201,7 +201,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
self.send_data_to_clipboard(str(item))
def slot_rename_function(self, action):
""" slot connected to custom context menu
"""slot connected to custom context menu
allows user to select a edit a function name and push
changes to IDA
@@ -216,7 +216,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
item.setIsEditable(False)
def slot_custom_context_menu_requested(self, pos):
""" slot connected to custom context menu request
"""slot connected to custom context menu request
displays custom context menu to user containing action
relevant to the data item selected
@@ -243,7 +243,7 @@ class CapaExplorerQtreeView(QtWidgets.QTreeView):
self.show_custom_context_menu(menu, pos)
def slot_double_click(self, model_index):
""" slot connected to double click event
"""slot connected to double click event
@param model_index: QModelIndex*
"""

View File

@@ -102,6 +102,9 @@ def collect_metadata():
"sha256": sha256,
"path": idaapi.get_input_file_path(),
},
"analysis": {"format": idaapi.get_file_type_name(), "extractor": "ida",},
"analysis": {
"format": idaapi.get_file_type_name(),
"extractor": "ida",
},
"version": capa.version.__version__,
}

View File

@@ -30,7 +30,7 @@ logger = logging.getLogger("capa")
class CapaExplorerIdaHooks(idaapi.UI_Hooks):
def __init__(self, screen_ea_changed_hook, action_hooks):
""" facilitate IDA UI hooks
"""facilitate IDA UI hooks
@param screen_ea_changed_hook: function hook for IDA screen ea changed
@param action_hooks: dict of IDA action handles
@@ -43,7 +43,7 @@ class CapaExplorerIdaHooks(idaapi.UI_Hooks):
self.process_action_meta = {}
def preprocess_action(self, name):
""" called prior to action completed
"""called prior to action completed
@param name: name of action defined by idagui.cfg
@@ -66,7 +66,7 @@ class CapaExplorerIdaHooks(idaapi.UI_Hooks):
self.reset()
def screen_ea_changed(self, curr_ea, prev_ea):
""" called after screen location is changed
"""called after screen location is changed
@param curr_ea: current location
@param prev_ea: prev location
@@ -300,7 +300,7 @@ class CapaExplorerForm(idaapi.PluginForm):
self.ida_hooks.unhook()
def ida_hook_rename(self, meta, post=False):
""" hook for IDA rename action
"""hook for IDA rename action
called twice, once before action and once after
action completes
@@ -322,7 +322,7 @@ class CapaExplorerForm(idaapi.PluginForm):
meta["prev_name"] = curr_name
def ida_hook_screen_ea_changed(self, widget, new_ea, old_ea):
""" hook for IDA screen ea changed
"""hook for IDA screen ea changed
@param widget: IDA widget type
@param new_ea: destination ea
@@ -508,7 +508,7 @@ class CapaExplorerForm(idaapi.PluginForm):
idaapi.info("%s reload completed." % PLUGIN_NAME)
def reset(self):
""" reset UI elements
"""reset UI elements
e.g. checkboxes and IDA highlighting
"""
@@ -518,7 +518,7 @@ class CapaExplorerForm(idaapi.PluginForm):
idaapi.info("%s reset completed." % PLUGIN_NAME)
def slot_menu_bar_hovered(self, action):
""" display menu action tooltip
"""display menu action tooltip
@param action: QtWidgets.QAction*
@@ -529,7 +529,7 @@ class CapaExplorerForm(idaapi.PluginForm):
)
def slot_checkbox_limit_by_changed(self):
""" slot activated if checkbox clicked
"""slot activated if checkbox clicked
if checked, configure function filter if screen location is located
in function, otherwise clear filter

View File

@@ -105,7 +105,12 @@ def find_capabilities(ruleset, extractor, disable_progress=None):
all_function_matches = collections.defaultdict(list)
all_bb_matches = collections.defaultdict(list)
meta = {"feature_counts": {"file": 0, "functions": {},}}
meta = {
"feature_counts": {
"file": 0,
"functions": {},
}
}
for f in tqdm.tqdm(list(extractor.get_functions()), disable=disable_progress, desc="matching", unit=" functions"):
function_matches, bb_matches, feature_count = find_function_capabilities(ruleset, extractor, f)

View File

@@ -152,7 +152,10 @@ def convert_match_to_result_document(rules, capabilities, result):
scope = rule.meta["scope"]
doc["node"] = {
"type": "statement",
"statement": {"type": "subscope", "subscope": scope,},
"statement": {
"type": "subscope",
"subscope": scope,
},
}
for location in doc["locations"]:
@@ -257,5 +260,7 @@ class CapaJsonObjectEncoder(json.JSONEncoder):
def render_json(meta, rules, capabilities):
return json.dumps(
convert_capabilities_to_result_document(meta, rules, capabilities), cls=CapaJsonObjectEncoder, sort_keys=True,
convert_capabilities_to_result_document(meta, rules, capabilities),
cls=CapaJsonObjectEncoder,
sort_keys=True,
)

View File

@@ -109,7 +109,12 @@ def render_attack(doc, ostream):
inner_rows.append("%s::%s %s" % (rutils.bold(technique), subtechnique, id))
else:
raise RuntimeError("unexpected ATT&CK spec format")
rows.append((rutils.bold(tactic.upper()), "\n".join(inner_rows),))
rows.append(
(
rutils.bold(tactic.upper()),
"\n".join(inner_rows),
)
)
if rows:
ostream.write(

View File

@@ -399,7 +399,11 @@ def lint_rule(ctx, rule):
print("")
print(
"%s%s %s"
% (" (nursery) " if is_nursery_rule(rule) else "", rule.name, ("(%s)" % category) if category else "",)
% (
" (nursery) " if is_nursery_rule(rule) else "",
rule.name,
("(%s)" % category) if category else "",
)
)
level = "WARN" if is_nursery_rule(rule) else "FAIL"
@@ -407,7 +411,12 @@ def lint_rule(ctx, rule):
for violation in violations:
print(
"%s %s: %s: %s"
% (" " if is_nursery_rule(rule) else "", level, violation.name, violation.recommendation,)
% (
" " if is_nursery_rule(rule) else "",
level,
violation.name,
violation.recommendation,
)
)
elif len(violations) == 0 and is_nursery_rule(rule):
@@ -487,7 +496,9 @@ def main(argv=None):
parser.add_argument("rules", type=str, help="Path to rules")
parser.add_argument("--samples", type=str, default=samples_path, help="Path to samples")
parser.add_argument(
"--thorough", action="store_true", help="Enable thorough linting - takes more time, but does a better job",
"--thorough",
action="store_true",
help="Enable thorough linting - takes more time, but does a better job",
)
parser.add_argument("-v", "--verbose", action="store_true", help="Enable debug logging")
parser.add_argument("-q", "--quiet", action="store_true", help="Disable all output but errors")

View File

@@ -42,7 +42,11 @@ setuptools.setup(
url="https://www.github.com/fireeye/capa",
packages=setuptools.find_packages(exclude=["tests"]),
package_dir={"capa": "capa"},
entry_points={"console_scripts": ["capa=capa.main:main",]},
entry_points={
"console_scripts": [
"capa=capa.main:main",
]
},
include_package_data=True,
install_requires=requirements,
extras_require={

View File

@@ -340,10 +340,20 @@ FEATURE_PRESENCE_TESTS = [
("mimikatz", "function=0x4556E5", capa.features.insn.API("advapi32.LsaQueryInformationPolicy"), True),
("mimikatz", "function=0x4556E5", capa.features.insn.API("LsaQueryInformationPolicy"), True),
# insn/api: x64
("kernel32-64", "function=0x180001010", capa.features.insn.API("RtlVirtualUnwind"), True,),
(
"kernel32-64",
"function=0x180001010",
capa.features.insn.API("RtlVirtualUnwind"),
True,
),
("kernel32-64", "function=0x180001010", capa.features.insn.API("RtlVirtualUnwind"), True),
# insn/api: x64 thunk
("kernel32-64", "function=0x1800202B0", capa.features.insn.API("RtlCaptureContext"), True,),
(
"kernel32-64",
"function=0x1800202B0",
capa.features.insn.API("RtlCaptureContext"),
True,
),
("kernel32-64", "function=0x1800202B0", capa.features.insn.API("RtlCaptureContext"), True),
# insn/api: resolve indirect calls
("c91887...", "function=0x401A77", capa.features.insn.API("kernel32.CreatePipe"), True),

View File

@@ -59,7 +59,13 @@ def test_some():
)
assert (
Some(2, [Number(1), Number(2), Number(3)]).evaluate(
{Number(0): {1}, Number(1): {1}, Number(2): {1}, Number(3): {1}, Number(4): {1},}
{
Number(0): {1},
Number(1): {1},
Number(2): {1},
Number(3): {1},
Number(4): {1},
}
)
== True
)
@@ -258,7 +264,9 @@ def test_match_matched_rules():
]
features, matches = capa.engine.match(
capa.engine.topologically_order_rules(rules), {capa.features.insn.Number(100): {1}}, 0x0,
capa.engine.topologically_order_rules(rules),
{capa.features.insn.Number(100): {1}},
0x0,
)
assert capa.features.MatchedRule("test rule1") in features
assert capa.features.MatchedRule("test rule2") in features
@@ -266,7 +274,9 @@ def test_match_matched_rules():
# the ordering of the rules must not matter,
# the engine should match rules in an appropriate order.
features, matches = capa.engine.match(
capa.engine.topologically_order_rules(reversed(rules)), {capa.features.insn.Number(100): {1}}, 0x0,
capa.engine.topologically_order_rules(reversed(rules)),
{capa.features.insn.Number(100): {1}},
0x0,
)
assert capa.features.MatchedRule("test rule1") in features
assert capa.features.MatchedRule("test rule2") in features
@@ -312,22 +322,30 @@ def test_regex():
),
]
features, matches = capa.engine.match(
capa.engine.topologically_order_rules(rules), {capa.features.insn.Number(100): {1}}, 0x0,
capa.engine.topologically_order_rules(rules),
{capa.features.insn.Number(100): {1}},
0x0,
)
assert capa.features.MatchedRule("test rule") not in features
features, matches = capa.engine.match(
capa.engine.topologically_order_rules(rules), {capa.features.String("aaaa"): {1}}, 0x0,
capa.engine.topologically_order_rules(rules),
{capa.features.String("aaaa"): {1}},
0x0,
)
assert capa.features.MatchedRule("test rule") not in features
features, matches = capa.engine.match(
capa.engine.topologically_order_rules(rules), {capa.features.String("aBBBBa"): {1}}, 0x0,
capa.engine.topologically_order_rules(rules),
{capa.features.String("aBBBBa"): {1}},
0x0,
)
assert capa.features.MatchedRule("test rule") not in features
features, matches = capa.engine.match(
capa.engine.topologically_order_rules(rules), {capa.features.String("abbbba"): {1}}, 0x0,
capa.engine.topologically_order_rules(rules),
{capa.features.String("abbbba"): {1}},
0x0,
)
assert capa.features.MatchedRule("test rule") in features
assert capa.features.MatchedRule("rule with implied wildcards") in features
@@ -350,7 +368,9 @@ def test_regex_ignorecase():
),
]
features, matches = capa.engine.match(
capa.engine.topologically_order_rules(rules), {capa.features.String("aBBBBa"): {1}}, 0x0,
capa.engine.topologically_order_rules(rules),
{capa.features.String("aBBBBa"): {1}},
0x0,
)
assert capa.features.MatchedRule("test rule") in features
@@ -429,7 +449,9 @@ def test_match_namespace():
]
features, matches = capa.engine.match(
capa.engine.topologically_order_rules(rules), {capa.features.insn.API("CreateFile"): {1}}, 0x0,
capa.engine.topologically_order_rules(rules),
{capa.features.insn.API("CreateFile"): {1}},
0x0,
)
assert "CreateFile API" in matches
assert "file-create" in matches
@@ -439,7 +461,9 @@ def test_match_namespace():
assert capa.features.MatchedRule("file/create/CreateFile") in features
features, matches = capa.engine.match(
capa.engine.topologically_order_rules(rules), {capa.features.insn.API("WriteFile"): {1}}, 0x0,
capa.engine.topologically_order_rules(rules),
{capa.features.insn.API("WriteFile"): {1}},
0x0,
)
assert "WriteFile API" in matches
assert "file-create" not in matches

View File

@@ -21,13 +21,19 @@ import capa.features.extractors
EXTRACTOR = capa.features.extractors.NullFeatureExtractor(
{
"base address": 0x401000,
"file features": [(0x402345, capa.features.Characteristic("embedded pe")),],
"file features": [
(0x402345, capa.features.Characteristic("embedded pe")),
],
"functions": {
0x401000: {
"features": [(0x401000, capa.features.Characteristic("indirect call")),],
"features": [
(0x401000, capa.features.Characteristic("indirect call")),
],
"basic blocks": {
0x401000: {
"features": [(0x401000, capa.features.Characteristic("tight loop")),],
"features": [
(0x401000, capa.features.Characteristic("tight loop")),
],
"instructions": {
0x401000: {
"features": [
@@ -35,7 +41,11 @@ EXTRACTOR = capa.features.extractors.NullFeatureExtractor(
(0x401000, capa.features.Characteristic("nzxor")),
],
},
0x401002: {"features": [(0x401002, capa.features.insn.Mnemonic("mov")),],},
0x401002: {
"features": [
(0x401002, capa.features.insn.Mnemonic("mov")),
],
},
},
},
},

View File

@@ -44,7 +44,17 @@ def test_main_single_rule(z9324d_extractor, tmpdir):
path = z9324d_extractor.path
rule_file = tmpdir.mkdir("capa").join("rule.yml")
rule_file.write(RULE_CONTENT)
assert capa.main.main([path, "-v", "-r", rule_file.strpath,]) == 0
assert (
capa.main.main(
[
path,
"-v",
"-r",
rule_file.strpath,
]
)
== 0
)
@pytest.mark.xfail(sys.version_info >= (3, 0), reason="vivsect only works on py2")

View File

@@ -680,12 +680,16 @@ def test_regex_values_always_string():
),
]
features, matches = capa.engine.match(
capa.engine.topologically_order_rules(rules), {capa.features.String("123"): {1}}, 0x0,
capa.engine.topologically_order_rules(rules),
{capa.features.String("123"): {1}},
0x0,
)
assert capa.features.MatchedRule("test rule") in features
features, matches = capa.engine.match(
capa.engine.topologically_order_rules(rules), {capa.features.String("0x123"): {1}}, 0x0,
capa.engine.topologically_order_rules(rules),
{capa.features.String("0x123"): {1}},
0x0,
)
assert capa.features.MatchedRule("test rule") in features

View File

@@ -11,7 +11,9 @@ from fixtures import *
@parametrize(
"sample,scope,feature,expected", FEATURE_PRESENCE_TESTS, indirect=["sample", "scope"],
"sample,scope,feature,expected",
FEATURE_PRESENCE_TESTS,
indirect=["sample", "scope"],
)
def test_viv_features(sample, scope, feature, expected):
with xfail(sys.version_info >= (3, 0), reason="vivsect only works on py2"):
@@ -19,7 +21,9 @@ def test_viv_features(sample, scope, feature, expected):
@parametrize(
"sample,scope,feature,expected", FEATURE_COUNT_TESTS, indirect=["sample", "scope"],
"sample,scope,feature,expected",
FEATURE_COUNT_TESTS,
indirect=["sample", "scope"],
)
def test_viv_feature_counts(sample, scope, feature, expected):
with xfail(sys.version_info >= (3, 0), reason="vivsect only works on py2"):