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): 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: args:
op (IDA op_t) op (IDA op_t)
@@ -62,7 +62,7 @@ def get_printable_len(op):
def is_mov_imm_to_stack(insn): def is_mov_imm_to_stack(insn):
""" verify instruction moves immediate onto stack """verify instruction moves immediate onto stack
args: args:
insn (IDA insn_t) insn (IDA insn_t)
@@ -80,7 +80,7 @@ def is_mov_imm_to_stack(insn):
def bb_contains_stackstring(f, bb): 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 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): def extract_bb_stackstring(f, bb):
""" extract stackstring indicators from basic block """extract stackstring indicators from basic block
args: args:
f (IDA func_t) f (IDA func_t)
@@ -109,7 +109,7 @@ def extract_bb_stackstring(f, bb):
def extract_bb_tight_loop(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: args:
f (IDA func_t) f (IDA func_t)
@@ -120,7 +120,7 @@ def extract_bb_tight_loop(f, bb):
def extract_features(f, bb): def extract_features(f, bb):
""" extract basic block features """extract basic block features
args: args:
f (IDA func_t) f (IDA func_t)

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ import idautils
def find_byte_sequence(start, end, seq): def find_byte_sequence(start, end, seq):
""" find byte sequence """find byte sequence
args: args:
start: min virtual address 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): def get_functions(start=None, end=None, skip_thunks=False, skip_libs=False):
""" get functions, range optional """get functions, range optional
args: args:
start: min virtual address 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): 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: args:
skip_header_segments: IDA may load header segments - skip if set 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): 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 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): def get_instructions_in_range(start, end):
""" yield instructions in range """yield instructions in range
args: args:
start: virtual address (inclusive) start: virtual address (inclusive)
@@ -183,7 +183,7 @@ def find_string_at(ea, min=4):
def get_op_phrase_info(op): def get_op_phrase_info(op):
""" parse phrase features from operand """parse phrase features from operand
Pretty much dup of sark's implementation: Pretty much dup of sark's implementation:
https://github.com/tmr232/Sark/blob/master/sark/code/instruction.py#L28-L73 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): def mask_op_val(op):
""" mask value by data type """mask value by data type
necessary due to a bug in AMD64 necessary due to a bug in AMD64
@@ -289,7 +289,7 @@ def mask_op_val(op):
def is_function_recursive(f): def is_function_recursive(f):
""" check if function is recursive """check if function is recursive
args: args:
f (IDA func_t) f (IDA func_t)
@@ -301,7 +301,7 @@ def is_function_recursive(f):
def is_basic_block_tight_loop(bb): 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 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): def extract_insn_api_features(f, bb, insn):
""" parse instruction API features """parse instruction API features
args: args:
f (IDA func_t) f (IDA func_t)
@@ -78,7 +78,7 @@ def extract_insn_api_features(f, bb, insn):
def extract_insn_number_features(f, bb, insn): def extract_insn_number_features(f, bb, insn):
""" parse instruction number features """parse instruction number features
args: args:
f (IDA func_t) f (IDA func_t)
@@ -109,7 +109,7 @@ def extract_insn_number_features(f, bb, insn):
def extract_insn_bytes_features(f, bb, insn): def extract_insn_bytes_features(f, bb, insn):
""" parse referenced byte sequences """parse referenced byte sequences
args: args:
f (IDA func_t) f (IDA func_t)
@@ -127,7 +127,7 @@ def extract_insn_bytes_features(f, bb, insn):
def extract_insn_string_features(f, bb, insn): def extract_insn_string_features(f, bb, insn):
""" parse instruction string features """parse instruction string features
args: args:
f (IDA func_t) f (IDA func_t)
@@ -145,7 +145,7 @@ def extract_insn_string_features(f, bb, insn):
def extract_insn_offset_features(f, bb, insn): def extract_insn_offset_features(f, bb, insn):
""" parse instruction structure offset features """parse instruction structure offset features
args: args:
f (IDA func_t) f (IDA func_t)
@@ -175,7 +175,7 @@ def extract_insn_offset_features(f, bb, insn):
def contains_stack_cookie_keywords(s): def contains_stack_cookie_keywords(s):
""" check if string contains stack cookie keywords """check if string contains stack cookie keywords
Examples: Examples:
xor ecx, ebp ; StackCookie xor ecx, ebp ; StackCookie
@@ -190,7 +190,7 @@ def contains_stack_cookie_keywords(s):
def bb_stack_cookie_registers(bb): 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 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): 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 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): def extract_insn_mnemonic_features(f, bb, insn):
""" parse instruction mnemonic features """parse instruction mnemonic features
args: args:
f (IDA func_t) 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): 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 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): def extract_insn_segment_access_features(f, bb, insn):
""" parse instruction fs or gs access """parse instruction fs or gs access
TODO: TODO:
IDA should be able to do this... 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): 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: args:
f (IDA func_t) 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): 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 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): 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 does not include calls like => call ds:dword_ABD4974
most relevant at the function or basic block scope; 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): def extract_features(f, bb, insn):
""" extract instruction features """extract instruction features
args: args:
f (IDA func_t) f (IDA func_t)

View File

@@ -11,7 +11,7 @@ from networkx.algorithms.components import strongly_connected_components
def has_loop(edges, threshold=2): 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: args:
edges: list of edge sets representing a directed graph i.e. [(1, 2), (2, 1)] 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: returns:
str: the serialized features. 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(): for feature, va in extractor.extract_file_features():
ret["scopes"]["file"].append(serialize_feature(feature) + (hex(va), ())) ret["scopes"]["file"].append(serialize_feature(feature) + (hex(va), ()))
@@ -99,7 +108,16 @@ def dumps(extractor):
ret["functions"][hex(f)][hex(bb)] = [] ret["functions"][hex(f)][hex(bb)] = []
for feature, va in extractor.extract_basic_block_features(f, 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( for insnva, insn in sorted(
[(insn.__int__(), insn) for insn in extractor.get_instructions(f, bb)], key=lambda p: p[0] [(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): for feature, va in extractor.extract_insn_features(f, bb, insn):
ret["scopes"]["instruction"].append( 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) return json.dumps(ret)

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ class CapaExplorerSortFilterProxyModel(QtCore.QSortFilterProxyModel):
super(CapaExplorerSortFilterProxyModel, self).__init__(parent) super(CapaExplorerSortFilterProxyModel, self).__init__(parent)
def lessThan(self, left, right): 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 left: QModelIndex*
@param right: QModelIndex* @param right: QModelIndex*
@@ -40,7 +40,7 @@ class CapaExplorerSortFilterProxyModel(QtCore.QSortFilterProxyModel):
return ldata.lower() < rdata.lower() return ldata.lower() < rdata.lower()
def filterAcceptsRow(self, row, parent): 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 should be included in the model; otherwise returns false
@param row: int @param row: int
@@ -63,7 +63,7 @@ class CapaExplorerSortFilterProxyModel(QtCore.QSortFilterProxyModel):
return False return False
def add_single_string_filter(self, column, string): def add_single_string_filter(self, column, string):
""" add fixed string filter """add fixed string filter
@param column: key column @param column: key column
@param string: string to sort @param string: string to sort

View File

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

View File

@@ -102,6 +102,9 @@ def collect_metadata():
"sha256": sha256, "sha256": sha256,
"path": idaapi.get_input_file_path(), "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__, "version": capa.version.__version__,
} }

View File

@@ -30,7 +30,7 @@ logger = logging.getLogger("capa")
class CapaExplorerIdaHooks(idaapi.UI_Hooks): class CapaExplorerIdaHooks(idaapi.UI_Hooks):
def __init__(self, screen_ea_changed_hook, action_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 screen_ea_changed_hook: function hook for IDA screen ea changed
@param action_hooks: dict of IDA action handles @param action_hooks: dict of IDA action handles
@@ -43,7 +43,7 @@ class CapaExplorerIdaHooks(idaapi.UI_Hooks):
self.process_action_meta = {} self.process_action_meta = {}
def preprocess_action(self, name): def preprocess_action(self, name):
""" called prior to action completed """called prior to action completed
@param name: name of action defined by idagui.cfg @param name: name of action defined by idagui.cfg
@@ -66,7 +66,7 @@ class CapaExplorerIdaHooks(idaapi.UI_Hooks):
self.reset() self.reset()
def screen_ea_changed(self, curr_ea, prev_ea): 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 curr_ea: current location
@param prev_ea: prev location @param prev_ea: prev location
@@ -300,7 +300,7 @@ class CapaExplorerForm(idaapi.PluginForm):
self.ida_hooks.unhook() self.ida_hooks.unhook()
def ida_hook_rename(self, meta, post=False): 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 called twice, once before action and once after
action completes action completes
@@ -322,7 +322,7 @@ class CapaExplorerForm(idaapi.PluginForm):
meta["prev_name"] = curr_name meta["prev_name"] = curr_name
def ida_hook_screen_ea_changed(self, widget, new_ea, old_ea): 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 widget: IDA widget type
@param new_ea: destination ea @param new_ea: destination ea
@@ -508,7 +508,7 @@ class CapaExplorerForm(idaapi.PluginForm):
idaapi.info("%s reload completed." % PLUGIN_NAME) idaapi.info("%s reload completed." % PLUGIN_NAME)
def reset(self): def reset(self):
""" reset UI elements """reset UI elements
e.g. checkboxes and IDA highlighting e.g. checkboxes and IDA highlighting
""" """
@@ -518,7 +518,7 @@ class CapaExplorerForm(idaapi.PluginForm):
idaapi.info("%s reset completed." % PLUGIN_NAME) idaapi.info("%s reset completed." % PLUGIN_NAME)
def slot_menu_bar_hovered(self, action): def slot_menu_bar_hovered(self, action):
""" display menu action tooltip """display menu action tooltip
@param action: QtWidgets.QAction* @param action: QtWidgets.QAction*
@@ -529,7 +529,7 @@ class CapaExplorerForm(idaapi.PluginForm):
) )
def slot_checkbox_limit_by_changed(self): 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 if checked, configure function filter if screen location is located
in function, otherwise clear filter 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_function_matches = collections.defaultdict(list)
all_bb_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"): 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) 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"] scope = rule.meta["scope"]
doc["node"] = { doc["node"] = {
"type": "statement", "type": "statement",
"statement": {"type": "subscope", "subscope": scope,}, "statement": {
"type": "subscope",
"subscope": scope,
},
} }
for location in doc["locations"]: for location in doc["locations"]:
@@ -257,5 +260,7 @@ class CapaJsonObjectEncoder(json.JSONEncoder):
def render_json(meta, rules, capabilities): def render_json(meta, rules, capabilities):
return json.dumps( 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)) inner_rows.append("%s::%s %s" % (rutils.bold(technique), subtechnique, id))
else: else:
raise RuntimeError("unexpected ATT&CK spec format") 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: if rows:
ostream.write( ostream.write(

View File

@@ -399,7 +399,11 @@ def lint_rule(ctx, rule):
print("") print("")
print( print(
"%s%s %s" "%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" level = "WARN" if is_nursery_rule(rule) else "FAIL"
@@ -407,7 +411,12 @@ def lint_rule(ctx, rule):
for violation in violations: for violation in violations:
print( print(
"%s %s: %s: %s" "%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): 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("rules", type=str, help="Path to rules")
parser.add_argument("--samples", type=str, default=samples_path, help="Path to samples") parser.add_argument("--samples", type=str, default=samples_path, help="Path to samples")
parser.add_argument( 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("-v", "--verbose", action="store_true", help="Enable debug logging")
parser.add_argument("-q", "--quiet", action="store_true", help="Disable all output but errors") 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", url="https://www.github.com/fireeye/capa",
packages=setuptools.find_packages(exclude=["tests"]), packages=setuptools.find_packages(exclude=["tests"]),
package_dir={"capa": "capa"}, package_dir={"capa": "capa"},
entry_points={"console_scripts": ["capa=capa.main:main",]}, entry_points={
"console_scripts": [
"capa=capa.main:main",
]
},
include_package_data=True, include_package_data=True,
install_requires=requirements, install_requires=requirements,
extras_require={ 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("advapi32.LsaQueryInformationPolicy"), True),
("mimikatz", "function=0x4556E5", capa.features.insn.API("LsaQueryInformationPolicy"), True), ("mimikatz", "function=0x4556E5", capa.features.insn.API("LsaQueryInformationPolicy"), True),
# insn/api: x64 # 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), ("kernel32-64", "function=0x180001010", capa.features.insn.API("RtlVirtualUnwind"), True),
# insn/api: x64 thunk # 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), ("kernel32-64", "function=0x1800202B0", capa.features.insn.API("RtlCaptureContext"), True),
# insn/api: resolve indirect calls # insn/api: resolve indirect calls
("c91887...", "function=0x401A77", capa.features.insn.API("kernel32.CreatePipe"), True), ("c91887...", "function=0x401A77", capa.features.insn.API("kernel32.CreatePipe"), True),

View File

@@ -59,7 +59,13 @@ def test_some():
) )
assert ( assert (
Some(2, [Number(1), Number(2), Number(3)]).evaluate( 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 == True
) )
@@ -258,7 +264,9 @@ def test_match_matched_rules():
] ]
features, matches = capa.engine.match( 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 rule1") in features
assert capa.features.MatchedRule("test rule2") 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 ordering of the rules must not matter,
# the engine should match rules in an appropriate order. # the engine should match rules in an appropriate order.
features, matches = capa.engine.match( 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 rule1") in features
assert capa.features.MatchedRule("test rule2") in features assert capa.features.MatchedRule("test rule2") in features
@@ -312,22 +322,30 @@ def test_regex():
), ),
] ]
features, matches = capa.engine.match( 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 assert capa.features.MatchedRule("test rule") not in features
features, matches = capa.engine.match( 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 assert capa.features.MatchedRule("test rule") not in features
features, matches = capa.engine.match( 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 assert capa.features.MatchedRule("test rule") not in features
features, matches = capa.engine.match( 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("test rule") in features
assert capa.features.MatchedRule("rule with implied wildcards") 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( 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("test rule") in features
@@ -429,7 +449,9 @@ def test_match_namespace():
] ]
features, matches = capa.engine.match( 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 "CreateFile API" in matches
assert "file-create" in matches assert "file-create" in matches
@@ -439,7 +461,9 @@ def test_match_namespace():
assert capa.features.MatchedRule("file/create/CreateFile") in features assert capa.features.MatchedRule("file/create/CreateFile") in features
features, matches = capa.engine.match( 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 "WriteFile API" in matches
assert "file-create" not in matches assert "file-create" not in matches

View File

@@ -21,13 +21,19 @@ import capa.features.extractors
EXTRACTOR = capa.features.extractors.NullFeatureExtractor( EXTRACTOR = capa.features.extractors.NullFeatureExtractor(
{ {
"base address": 0x401000, "base address": 0x401000,
"file features": [(0x402345, capa.features.Characteristic("embedded pe")),], "file features": [
(0x402345, capa.features.Characteristic("embedded pe")),
],
"functions": { "functions": {
0x401000: { 0x401000: {
"features": [(0x401000, capa.features.Characteristic("indirect call")),], "features": [
(0x401000, capa.features.Characteristic("indirect call")),
],
"basic blocks": { "basic blocks": {
0x401000: { 0x401000: {
"features": [(0x401000, capa.features.Characteristic("tight loop")),], "features": [
(0x401000, capa.features.Characteristic("tight loop")),
],
"instructions": { "instructions": {
0x401000: { 0x401000: {
"features": [ "features": [
@@ -35,7 +41,11 @@ EXTRACTOR = capa.features.extractors.NullFeatureExtractor(
(0x401000, capa.features.Characteristic("nzxor")), (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 path = z9324d_extractor.path
rule_file = tmpdir.mkdir("capa").join("rule.yml") rule_file = tmpdir.mkdir("capa").join("rule.yml")
rule_file.write(RULE_CONTENT) 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") @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( 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 assert capa.features.MatchedRule("test rule") in features
features, matches = capa.engine.match( 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 assert capa.features.MatchedRule("test rule") in features

View File

@@ -11,7 +11,9 @@ from fixtures import *
@parametrize( @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): def test_viv_features(sample, scope, feature, expected):
with xfail(sys.version_info >= (3, 0), reason="vivsect only works on py2"): 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( @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): def test_viv_feature_counts(sample, scope, feature, expected):
with xfail(sys.version_info >= (3, 0), reason="vivsect only works on py2"): with xfail(sys.version_info >= (3, 0), reason="vivsect only works on py2"):