mirror of
https://github.com/mandiant/capa.git
synced 2025-12-22 07:10:29 -08:00
black 20.8b1 updates
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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*
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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__,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
6
setup.py
6
setup.py
@@ -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={
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")),
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"):
|
||||||
|
|||||||
Reference in New Issue
Block a user