mirror of
https://github.com/mandiant/capa.git
synced 2025-12-22 23:26:21 -08:00
black 20.8b1 updates
This commit is contained in:
@@ -20,10 +20,10 @@ 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)
|
||||||
"""
|
"""
|
||||||
op_val = capa.features.extractors.ida.helpers.mask_op_val(op)
|
op_val = capa.features.extractors.ida.helpers.mask_op_val(op)
|
||||||
|
|
||||||
@@ -62,10 +62,10 @@ 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)
|
||||||
"""
|
"""
|
||||||
if insn.Op2.type != idaapi.o_imm:
|
if insn.Op2.type != idaapi.o_imm:
|
||||||
return False
|
return False
|
||||||
@@ -80,13 +80,13 @@ 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
|
||||||
|
|
||||||
args:
|
args:
|
||||||
f (IDA func_t)
|
f (IDA func_t)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
"""
|
"""
|
||||||
count = 0
|
count = 0
|
||||||
for insn in capa.features.extractors.ida.helpers.get_instructions_in_range(bb.start_ea, bb.end_ea):
|
for insn in capa.features.extractors.ida.helpers.get_instructions_in_range(bb.start_ea, bb.end_ea):
|
||||||
@@ -98,33 +98,33 @@ 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)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
"""
|
"""
|
||||||
if bb_contains_stackstring(f, bb):
|
if bb_contains_stackstring(f, bb):
|
||||||
yield Characteristic("stack string"), bb.start_ea
|
yield Characteristic("stack string"), bb.start_ea
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
"""
|
"""
|
||||||
if capa.features.extractors.ida.helpers.is_basic_block_tight_loop(bb):
|
if capa.features.extractors.ida.helpers.is_basic_block_tight_loop(bb):
|
||||||
yield Characteristic("tight loop"), bb.start_ea
|
yield Characteristic("tight loop"), bb.start_ea
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
"""
|
"""
|
||||||
for bb_handler in BASIC_BLOCK_HANDLERS:
|
for bb_handler in BASIC_BLOCK_HANDLERS:
|
||||||
for (feature, ea) in bb_handler(f, bb):
|
for (feature, ea) in bb_handler(f, bb):
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ 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
|
||||||
|
|
||||||
args:
|
args:
|
||||||
seg (IDA segment_t)
|
seg (IDA segment_t)
|
||||||
"""
|
"""
|
||||||
seg_max = seg.end_ea
|
seg_max = seg.end_ea
|
||||||
mz_xor = [
|
mz_xor = [
|
||||||
@@ -67,11 +67,11 @@ 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
|
||||||
- Check 'Load resource sections' when opening binary in IDA manually
|
- Check 'Load resource sections' when opening binary in IDA manually
|
||||||
"""
|
"""
|
||||||
for seg in capa.features.extractors.ida.helpers.get_segments(skip_header_segments=True):
|
for seg in capa.features.extractors.ida.helpers.get_segments(skip_header_segments=True):
|
||||||
for (ea, _) in check_segment_for_pe(seg):
|
for (ea, _) in check_segment_for_pe(seg):
|
||||||
@@ -85,15 +85,15 @@ 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
|
||||||
|
|
||||||
2. imports by name, results in two features to support importname-only
|
2. imports by name, results in two features to support importname-only
|
||||||
matching:
|
matching:
|
||||||
- modulename.importname
|
- modulename.importname
|
||||||
- importname
|
- importname
|
||||||
"""
|
"""
|
||||||
for (ea, info) in capa.features.extractors.ida.helpers.get_file_imports().items():
|
for (ea, info) in capa.features.extractors.ida.helpers.get_file_imports().items():
|
||||||
if info[1]:
|
if info[1]:
|
||||||
@@ -104,22 +104,22 @@ 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
|
||||||
- Check 'Load resource sections' when opening binary in IDA manually
|
- Check 'Load resource sections' when opening binary in IDA manually
|
||||||
"""
|
"""
|
||||||
for seg in capa.features.extractors.ida.helpers.get_segments(skip_header_segments=True):
|
for seg in capa.features.extractors.ida.helpers.get_segments(skip_header_segments=True):
|
||||||
yield Section(idaapi.get_segm_name(seg)), seg.start_ea
|
yield Section(idaapi.get_segm_name(seg)), seg.start_ea
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
- Check 'Load resource sections' when opening binary in IDA manually
|
- Check 'Load resource sections' when opening binary in IDA manually
|
||||||
"""
|
"""
|
||||||
for seg in capa.features.extractors.ida.helpers.get_segments():
|
for seg in capa.features.extractors.ida.helpers.get_segments():
|
||||||
seg_buff = capa.features.extractors.ida.helpers.get_segment_buffer(seg)
|
seg_buff = capa.features.extractors.ida.helpers.get_segment_buffer(seg)
|
||||||
|
|||||||
@@ -15,20 +15,20 @@ 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)
|
||||||
"""
|
"""
|
||||||
for ea in idautils.CodeRefsTo(f.start_ea, True):
|
for ea in idautils.CodeRefsTo(f.start_ea, True):
|
||||||
yield Characteristic("calls to"), ea
|
yield Characteristic("calls to"), ea
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
"""
|
"""
|
||||||
edges = []
|
edges = []
|
||||||
|
|
||||||
@@ -42,20 +42,20 @@ 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)
|
||||||
"""
|
"""
|
||||||
if capa.features.extractors.ida.helpers.is_function_recursive(f):
|
if capa.features.extractors.ida.helpers.is_function_recursive(f):
|
||||||
yield Characteristic("recursive call"), f.start_ea
|
yield Characteristic("recursive call"), f.start_ea
|
||||||
|
|
||||||
|
|
||||||
def extract_features(f):
|
def extract_features(f):
|
||||||
""" extract function features
|
"""extract function features
|
||||||
|
|
||||||
arg:
|
arg:
|
||||||
f (IDA func_t)
|
f (IDA func_t)
|
||||||
"""
|
"""
|
||||||
for func_handler in FUNCTION_HANDLERS:
|
for func_handler in FUNCTION_HANDLERS:
|
||||||
for (feature, ea) in func_handler(f):
|
for (feature, ea) in func_handler(f):
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ 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
|
||||||
end: max virtual address
|
end: max virtual address
|
||||||
seq: bytes to search e.g. b'\x01\x03'
|
seq: bytes to search e.g. b'\x01\x03'
|
||||||
"""
|
"""
|
||||||
if sys.version_info[0] >= 3:
|
if sys.version_info[0] >= 3:
|
||||||
return idaapi.find_binary(start, end, " ".join(["%02x" % b for b in seq]), 0, idaapi.SEARCH_DOWN)
|
return idaapi.find_binary(start, end, " ".join(["%02x" % b for b in seq]), 0, idaapi.SEARCH_DOWN)
|
||||||
@@ -29,14 +29,14 @@ 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
|
||||||
end: max virtual address
|
end: max virtual address
|
||||||
|
|
||||||
ret:
|
ret:
|
||||||
yield func_t*
|
yield func_t*
|
||||||
"""
|
"""
|
||||||
for ea in idautils.Functions(start=start, end=end):
|
for ea in idautils.Functions(start=start, end=end):
|
||||||
f = idaapi.get_func(ea)
|
f = idaapi.get_func(ea)
|
||||||
@@ -45,10 +45,10 @@ 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
|
||||||
"""
|
"""
|
||||||
for n in range(idaapi.get_segm_qty()):
|
for n in range(idaapi.get_segm_qty()):
|
||||||
seg = idaapi.getnseg(n)
|
seg = idaapi.getnseg(n)
|
||||||
@@ -57,9 +57,9 @@ 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
|
||||||
"""
|
"""
|
||||||
buff = b""
|
buff = b""
|
||||||
sz = seg.end_ea - seg.start_ea
|
sz = seg.end_ea - seg.start_ea
|
||||||
@@ -97,13 +97,13 @@ 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)
|
||||||
end: virtual address (exclusive)
|
end: virtual address (exclusive)
|
||||||
yield:
|
yield:
|
||||||
(insn_t*)
|
(insn_t*)
|
||||||
"""
|
"""
|
||||||
for head in idautils.Heads(start, end):
|
for head in idautils.Heads(start, end):
|
||||||
insn = idautils.DecodeInstruction(head)
|
insn = idautils.DecodeInstruction(head)
|
||||||
@@ -183,10 +183,10 @@ 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
|
||||||
"""
|
"""
|
||||||
if op.type not in (idaapi.o_phrase, idaapi.o_displ):
|
if op.type not in (idaapi.o_phrase, idaapi.o_displ):
|
||||||
return {}
|
return {}
|
||||||
@@ -269,15 +269,15 @@ 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
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
.rsrc:0054C12C mov [ebp+var_4], 0FFFFFFFFh
|
.rsrc:0054C12C mov [ebp+var_4], 0FFFFFFFFh
|
||||||
|
|
||||||
insn.Op2.dtype == idaapi.dt_dword
|
insn.Op2.dtype == idaapi.dt_dword
|
||||||
insn.Op2.value == 0xffffffffffffffff
|
insn.Op2.value == 0xffffffffffffffff
|
||||||
"""
|
"""
|
||||||
masks = {
|
masks = {
|
||||||
idaapi.dt_byte: 0xFF,
|
idaapi.dt_byte: 0xFF,
|
||||||
@@ -289,10 +289,10 @@ 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)
|
||||||
"""
|
"""
|
||||||
for ref in idautils.CodeRefsTo(f.start_ea, True):
|
for ref in idautils.CodeRefsTo(f.start_ea, True):
|
||||||
if f.contains(ref):
|
if f.contains(ref):
|
||||||
@@ -301,13 +301,13 @@ 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
|
||||||
|
|
||||||
args:
|
args:
|
||||||
f (IDA func_t)
|
f (IDA func_t)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
"""
|
"""
|
||||||
bb_end = idc.prev_head(bb.end_ea)
|
bb_end = idc.prev_head(bb.end_ea)
|
||||||
if bb.start_ea < bb_end:
|
if bb.start_ea < bb_end:
|
||||||
|
|||||||
@@ -62,15 +62,15 @@ 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)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
insn (IDA insn_t)
|
insn (IDA insn_t)
|
||||||
|
|
||||||
example:
|
example:
|
||||||
call dword [0x00473038]
|
call dword [0x00473038]
|
||||||
"""
|
"""
|
||||||
for api in check_for_api_call(f.ctx, insn):
|
for api in check_for_api_call(f.ctx, insn):
|
||||||
for (feature, ea) in capa.features.extractors.helpers.generate_api_features(api, insn.ea):
|
for (feature, ea) in capa.features.extractors.helpers.generate_api_features(api, insn.ea):
|
||||||
@@ -78,15 +78,15 @@ 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)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
insn (IDA insn_t)
|
insn (IDA insn_t)
|
||||||
|
|
||||||
example:
|
example:
|
||||||
push 3136B0h ; dwControlCode
|
push 3136B0h ; dwControlCode
|
||||||
"""
|
"""
|
||||||
if idaapi.is_ret_insn(insn):
|
if idaapi.is_ret_insn(insn):
|
||||||
# skip things like:
|
# skip things like:
|
||||||
@@ -109,15 +109,15 @@ 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)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
insn (IDA insn_t)
|
insn (IDA insn_t)
|
||||||
|
|
||||||
example:
|
example:
|
||||||
push offset iid_004118d4_IShellLinkA ; riid
|
push offset iid_004118d4_IShellLinkA ; riid
|
||||||
"""
|
"""
|
||||||
ref = capa.features.extractors.ida.helpers.find_data_reference_from_insn(insn)
|
ref = capa.features.extractors.ida.helpers.find_data_reference_from_insn(insn)
|
||||||
if ref != insn.ea:
|
if ref != insn.ea:
|
||||||
@@ -127,15 +127,15 @@ 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)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
insn (IDA insn_t)
|
insn (IDA insn_t)
|
||||||
|
|
||||||
example:
|
example:
|
||||||
push offset aAcr ; "ACR > "
|
push offset aAcr ; "ACR > "
|
||||||
"""
|
"""
|
||||||
ref = capa.features.extractors.ida.helpers.find_data_reference_from_insn(insn)
|
ref = capa.features.extractors.ida.helpers.find_data_reference_from_insn(insn)
|
||||||
if ref != insn.ea:
|
if ref != insn.ea:
|
||||||
@@ -145,15 +145,15 @@ 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)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
insn (IDA insn_t)
|
insn (IDA insn_t)
|
||||||
|
|
||||||
example:
|
example:
|
||||||
.text:0040112F cmp [esi+4], ebx
|
.text:0040112F cmp [esi+4], ebx
|
||||||
"""
|
"""
|
||||||
for op in capa.features.extractors.ida.helpers.get_insn_ops(insn, target_ops=(idaapi.o_phrase, idaapi.o_displ)):
|
for op in capa.features.extractors.ida.helpers.get_insn_ops(insn, target_ops=(idaapi.o_phrase, idaapi.o_displ)):
|
||||||
if capa.features.extractors.ida.helpers.is_op_stack_var(insn.ea, op.n):
|
if capa.features.extractors.ida.helpers.is_op_stack_var(insn.ea, op.n):
|
||||||
@@ -175,11 +175,11 @@ 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
|
||||||
mov eax, ___security_cookie
|
mov eax, ___security_cookie
|
||||||
"""
|
"""
|
||||||
if not s:
|
if not s:
|
||||||
return False
|
return False
|
||||||
@@ -190,30 +190,30 @@ 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
|
||||||
|
|
||||||
assume instruction that sets stack cookie and nzxor exist in same block
|
assume instruction that sets stack cookie and nzxor exist in same block
|
||||||
and stack cookie register is not modified prior to nzxor
|
and stack cookie register is not modified prior to nzxor
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
.text:004062DA mov eax, ___security_cookie <-- stack cookie
|
.text:004062DA mov eax, ___security_cookie <-- stack cookie
|
||||||
.text:004062DF mov ecx, eax
|
.text:004062DF mov ecx, eax
|
||||||
.text:004062E1 mov ebx, [esi]
|
.text:004062E1 mov ebx, [esi]
|
||||||
.text:004062E3 and ecx, 1Fh
|
.text:004062E3 and ecx, 1Fh
|
||||||
.text:004062E6 mov edi, [esi+4]
|
.text:004062E6 mov edi, [esi+4]
|
||||||
.text:004062E9 xor ebx, eax
|
.text:004062E9 xor ebx, eax
|
||||||
.text:004062EB mov esi, [esi+8]
|
.text:004062EB mov esi, [esi+8]
|
||||||
.text:004062EE xor edi, eax <-- ignore
|
.text:004062EE xor edi, eax <-- ignore
|
||||||
.text:004062F0 xor esi, eax <-- ignore
|
.text:004062F0 xor esi, eax <-- ignore
|
||||||
.text:004062F2 ror edi, cl
|
.text:004062F2 ror edi, cl
|
||||||
.text:004062F4 ror esi, cl
|
.text:004062F4 ror esi, cl
|
||||||
.text:004062F6 ror ebx, cl
|
.text:004062F6 ror ebx, cl
|
||||||
.text:004062F8 cmp edi, esi
|
.text:004062F8 cmp edi, esi
|
||||||
.text:004062FA jnz loc_40639D
|
.text:004062FA jnz loc_40639D
|
||||||
|
|
||||||
TODO: this is expensive, but necessary?...
|
TODO: this is expensive, but necessary?...
|
||||||
"""
|
"""
|
||||||
for insn in capa.features.extractors.ida.helpers.get_instructions_in_range(bb.start_ea, bb.end_ea):
|
for insn in capa.features.extractors.ida.helpers.get_instructions_in_range(bb.start_ea, bb.end_ea):
|
||||||
if contains_stack_cookie_keywords(idc.GetDisasm(insn.ea)):
|
if contains_stack_cookie_keywords(idc.GetDisasm(insn.ea)):
|
||||||
@@ -239,14 +239,14 @@ 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
|
||||||
|
|
||||||
args:
|
args:
|
||||||
f (IDA func_t)
|
f (IDA func_t)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
insn (IDA insn_t)
|
insn (IDA insn_t)
|
||||||
"""
|
"""
|
||||||
if insn.itype != idaapi.NN_xor:
|
if insn.itype != idaapi.NN_xor:
|
||||||
return
|
return
|
||||||
@@ -258,23 +258,23 @@ 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)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
insn (IDA insn_t)
|
insn (IDA insn_t)
|
||||||
"""
|
"""
|
||||||
yield Mnemonic(insn.get_canon_mnem()), insn.ea
|
yield Mnemonic(insn.get_canon_mnem()), insn.ea
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
IDA should be able to do this..
|
IDA should be able to do this..
|
||||||
"""
|
"""
|
||||||
if insn.itype not in (idaapi.NN_push, idaapi.NN_mov):
|
if insn.itype not in (idaapi.NN_push, idaapi.NN_mov):
|
||||||
return
|
return
|
||||||
@@ -291,10 +291,10 @@ 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...
|
||||||
"""
|
"""
|
||||||
if all(map(lambda op: op.type != idaapi.o_mem, insn.ops)):
|
if all(map(lambda op: op.type != idaapi.o_mem, insn.ops)):
|
||||||
# try to optimize for only memory references
|
# try to optimize for only memory references
|
||||||
@@ -312,12 +312,12 @@ 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)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
insn (IDA insn_t)
|
insn (IDA insn_t)
|
||||||
"""
|
"""
|
||||||
for ref in idautils.CodeRefsFrom(insn.ea, False):
|
for ref in idautils.CodeRefsFrom(insn.ea, False):
|
||||||
if ref in get_imports(f.ctx).keys():
|
if ref in get_imports(f.ctx).keys():
|
||||||
@@ -332,14 +332,14 @@ 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
|
||||||
|
|
||||||
args:
|
args:
|
||||||
f (IDA func_t)
|
f (IDA func_t)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
insn (IDA insn_t)
|
insn (IDA insn_t)
|
||||||
"""
|
"""
|
||||||
if idaapi.is_call_insn(insn):
|
if idaapi.is_call_insn(insn):
|
||||||
for ref in idautils.CodeRefsFrom(insn.ea, False):
|
for ref in idautils.CodeRefsFrom(insn.ea, False):
|
||||||
@@ -347,28 +347,28 @@ 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;
|
||||||
however, its most efficient to extract at the instruction scope
|
however, its most efficient to extract at the instruction scope
|
||||||
|
|
||||||
args:
|
args:
|
||||||
f (IDA func_t)
|
f (IDA func_t)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
insn (IDA insn_t)
|
insn (IDA insn_t)
|
||||||
"""
|
"""
|
||||||
if idaapi.is_call_insn(insn) and idc.get_operand_type(insn.ea, 0) in (idc.o_reg, idc.o_phrase, idc.o_displ):
|
if idaapi.is_call_insn(insn) and idc.get_operand_type(insn.ea, 0) in (idc.o_reg, idc.o_phrase, idc.o_displ):
|
||||||
yield Characteristic("indirect call"), insn.ea
|
yield Characteristic("indirect call"), insn.ea
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
bb (IDA BasicBlock)
|
bb (IDA BasicBlock)
|
||||||
insn (IDA insn_t)
|
insn (IDA insn_t)
|
||||||
"""
|
"""
|
||||||
for inst_handler in INSTRUCTION_HANDLERS:
|
for inst_handler in INSTRUCTION_HANDLERS:
|
||||||
for (feature, ea) in inst_handler(f, bb, insn):
|
for (feature, ea) in inst_handler(f, bb, insn):
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ 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)]
|
||||||
threshold: min number of nodes contained in loop
|
threshold: min number of nodes contained in loop
|
||||||
|
|
||||||
returns:
|
returns:
|
||||||
bool
|
bool
|
||||||
"""
|
"""
|
||||||
g = nx.DiGraph()
|
g = nx.DiGraph()
|
||||||
g.add_edges_from(edges)
|
g.add_edges_from(edges)
|
||||||
|
|||||||
@@ -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,9 +17,9 @@ 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
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return display.split("(")[1].rstrip(")")
|
return display.split("(")[1].rstrip(")")
|
||||||
@@ -68,16 +68,16 @@ 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
|
||||||
"""
|
"""
|
||||||
return self.children[row]
|
return self.children[row]
|
||||||
|
|
||||||
|
|||||||
@@ -65,11 +65,11 @@ 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*
|
||||||
|
|
||||||
@retval column count
|
@retval column count
|
||||||
"""
|
"""
|
||||||
if model_index.isValid():
|
if model_index.isValid():
|
||||||
return model_index.internalPointer().columnCount()
|
return model_index.internalPointer().columnCount()
|
||||||
@@ -77,12 +77,12 @@ 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.*
|
||||||
|
|
||||||
@retval data to be displayed
|
@retval data to be displayed
|
||||||
"""
|
"""
|
||||||
if not model_index.isValid():
|
if not model_index.isValid():
|
||||||
return None
|
return None
|
||||||
@@ -151,11 +151,11 @@ 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*
|
||||||
|
|
||||||
@retval QtCore.Qt.ItemFlags
|
@retval QtCore.Qt.ItemFlags
|
||||||
"""
|
"""
|
||||||
if not model_index.isValid():
|
if not model_index.isValid():
|
||||||
return QtCore.Qt.NoItemFlags
|
return QtCore.Qt.NoItemFlags
|
||||||
@@ -163,13 +163,13 @@ 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
|
||||||
@param role: QtCore.Qt.DisplayRole
|
@param role: QtCore.Qt.DisplayRole
|
||||||
|
|
||||||
@retval header data list()
|
@retval header data list()
|
||||||
"""
|
"""
|
||||||
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
|
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
|
||||||
return self.root_node.data(section)
|
return self.root_node.data(section)
|
||||||
@@ -177,13 +177,13 @@ 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
|
||||||
@param parent: QModelIndex*
|
@param parent: QModelIndex*
|
||||||
|
|
||||||
@retval QModelIndex*
|
@retval QModelIndex*
|
||||||
"""
|
"""
|
||||||
if not self.hasIndex(row, column, parent):
|
if not self.hasIndex(row, column, parent):
|
||||||
return QtCore.QModelIndex()
|
return QtCore.QModelIndex()
|
||||||
@@ -201,13 +201,13 @@ 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
|
||||||
|
|
||||||
@param model_index: QModelIndex*
|
@param model_index: QModelIndex*
|
||||||
|
|
||||||
@retval QModelIndex*
|
@retval QModelIndex*
|
||||||
"""
|
"""
|
||||||
if not model_index.isValid():
|
if not model_index.isValid():
|
||||||
return QtCore.QModelIndex()
|
return QtCore.QModelIndex()
|
||||||
@@ -221,12 +221,12 @@ 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
|
||||||
|
|
||||||
@retval yield QModelIndex*
|
@retval yield QModelIndex*
|
||||||
"""
|
"""
|
||||||
visited = set()
|
visited = set()
|
||||||
stack = deque((model_index,))
|
stack = deque((model_index,))
|
||||||
@@ -248,10 +248,10 @@ 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
|
||||||
"""
|
"""
|
||||||
if not isinstance(
|
if not isinstance(
|
||||||
item, (CapaExplorerStringViewItem, CapaExplorerInstructionViewItem, CapaExplorerByteViewItem)
|
item, (CapaExplorerStringViewItem, CapaExplorerInstructionViewItem, CapaExplorerByteViewItem)
|
||||||
@@ -275,13 +275,13 @@ 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*
|
||||||
@param role: QtCore.Qt.EditRole
|
@param role: QtCore.Qt.EditRole
|
||||||
|
|
||||||
@retval True/False
|
@retval True/False
|
||||||
"""
|
"""
|
||||||
if not model_index.isValid():
|
if not model_index.isValid():
|
||||||
return False
|
return False
|
||||||
@@ -316,14 +316,14 @@ 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
|
||||||
|
|
||||||
@param model_index: QModelIndex*
|
@param model_index: QModelIndex*
|
||||||
|
|
||||||
@retval row count
|
@retval row count
|
||||||
"""
|
"""
|
||||||
if model_index.column() > 0:
|
if model_index.column() > 0:
|
||||||
return 0
|
return 0
|
||||||
@@ -336,16 +336,16 @@ 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
|
||||||
@param locations: locations of children (applies to range only?)
|
@param locations: locations of children (applies to range only?)
|
||||||
@param doc: capa result doc
|
@param doc: capa result doc
|
||||||
|
|
||||||
"statement": {
|
"statement": {
|
||||||
"type": "or"
|
"type": "or"
|
||||||
},
|
},
|
||||||
"""
|
"""
|
||||||
if statement["type"] in ("and", "or", "optional"):
|
if statement["type"] in ("and", "or", "optional"):
|
||||||
return CapaExplorerDefaultItem(parent, statement["type"])
|
return CapaExplorerDefaultItem(parent, statement["type"])
|
||||||
@@ -383,28 +383,28 @@ 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
|
||||||
@param doc: capa result doc
|
@param doc: capa result doc
|
||||||
|
|
||||||
"matches": {
|
"matches": {
|
||||||
"0": {
|
"0": {
|
||||||
"children": [],
|
"children": [],
|
||||||
"locations": [
|
"locations": [
|
||||||
4317184
|
4317184
|
||||||
],
|
],
|
||||||
"node": {
|
"node": {
|
||||||
"feature": {
|
"feature": {
|
||||||
"section": ".rsrc",
|
"section": ".rsrc",
|
||||||
"type": "section"
|
"type": "section"
|
||||||
},
|
|
||||||
"type": "feature"
|
|
||||||
},
|
},
|
||||||
"success": true
|
"type": "feature"
|
||||||
}
|
},
|
||||||
},
|
"success": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"""
|
"""
|
||||||
if not match["success"]:
|
if not match["success"]:
|
||||||
# TODO: display failed branches at some point? Help with debugging rules?
|
# TODO: display failed branches at some point? Help with debugging rules?
|
||||||
@@ -431,9 +431,9 @@ 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
|
||||||
"""
|
"""
|
||||||
# inform model that changes are about to occur
|
# inform model that changes are about to occur
|
||||||
self.beginResetModel()
|
self.beginResetModel()
|
||||||
@@ -457,18 +457,18 @@ 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
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
"feature": {
|
"feature": {
|
||||||
"bytes": "01 14 02 00 00 00 00 00 C0 00 00 00 00 00 00 46",
|
"bytes": "01 14 02 00 00 00 00 00 C0 00 00 00 00 00 00 46",
|
||||||
"description": "CLSID_ShellLink",
|
"description": "CLSID_ShellLink",
|
||||||
"type": "bytes"
|
"type": "bytes"
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes(01 14 02 00 00 00 00 00 C0 00 00 00 00 00 00 46 = CLSID_ShellLink)
|
bytes(01 14 02 00 00 00 00 00 C0 00 00 00 00 00 00 46 = CLSID_ShellLink)
|
||||||
"""
|
"""
|
||||||
if feature[feature["type"]]:
|
if feature[feature["type"]]:
|
||||||
if feature.get("description", ""):
|
if feature.get("description", ""):
|
||||||
@@ -479,25 +479,31 @@ 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
|
||||||
@param locations: locations identified for feature
|
@param locations: locations identified for feature
|
||||||
@param doc: capa doc
|
@param doc: capa doc
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
"feature": {
|
"feature": {
|
||||||
"description": "FILE_WRITE_DATA",
|
"description": "FILE_WRITE_DATA",
|
||||||
"number": "0x2",
|
"number": "0x2",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
display = self.capa_doc_feature_to_display(feature)
|
display = self.capa_doc_feature_to_display(feature)
|
||||||
|
|
||||||
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,20 +514,20 @@ 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
|
||||||
@param doc: capa feature doc
|
@param doc: capa feature doc
|
||||||
@param location: address of feature
|
@param location: address of feature
|
||||||
@param display: text to display in plugin ui
|
@param display: text to display in plugin ui
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
"feature": {
|
"feature": {
|
||||||
"description": "FILE_WRITE_DATA",
|
"description": "FILE_WRITE_DATA",
|
||||||
"number": "0x2",
|
"number": "0x2",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
# special handling for characteristic pending type
|
# special handling for characteristic pending type
|
||||||
if feature["type"] == "characteristic":
|
if feature["type"] == "characteristic":
|
||||||
@@ -575,10 +581,10 @@ 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
|
||||||
"""
|
"""
|
||||||
# create empty root index for search
|
# create empty root index for search
|
||||||
root_index = self.index(0, 0, QtCore.QModelIndex())
|
root_index = self.index(0, 0, QtCore.QModelIndex())
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ 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*
|
||||||
|
|
||||||
@retval True/False
|
@retval True/False
|
||||||
"""
|
"""
|
||||||
ldata = left.internalPointer().data(left.column())
|
ldata = left.internalPointer().data(left.column())
|
||||||
rdata = right.internalPointer().data(right.column())
|
rdata = right.internalPointer().data(right.column())
|
||||||
@@ -40,13 +40,13 @@ 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
|
||||||
@param parent: QModelIndex*
|
@param parent: QModelIndex*
|
||||||
|
|
||||||
@retval True/False
|
@retval True/False
|
||||||
"""
|
"""
|
||||||
if self.filter_accepts_row_self(row, parent):
|
if self.filter_accepts_row_self(row, parent):
|
||||||
return True
|
return True
|
||||||
@@ -63,10 +63,10 @@ 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
|
||||||
"""
|
"""
|
||||||
self.setFilterKeyColumn(column)
|
self.setFilterKeyColumn(column)
|
||||||
self.setFilterFixedString(string)
|
self.setFilterFixedString(string)
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ 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
|
||||||
|
|
||||||
view does not modify CapaExplorerDataModel directly - data
|
view does not modify CapaExplorerDataModel directly - data
|
||||||
modifications should be implemented in CapaExplorerDataModel
|
modifications should be implemented in CapaExplorerDataModel
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, model, parent=None):
|
def __init__(self, model, parent=None):
|
||||||
@@ -54,10 +54,10 @@ 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
|
||||||
"""
|
"""
|
||||||
self.collapseAll()
|
self.collapseAll()
|
||||||
self.resize_columns_to_content()
|
self.resize_columns_to_content()
|
||||||
@@ -67,31 +67,31 @@ 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*
|
||||||
|
|
||||||
@retval QObject*
|
@retval QObject*
|
||||||
"""
|
"""
|
||||||
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
|
||||||
"""
|
"""
|
||||||
clip = QtWidgets.QApplication.clipboard()
|
clip = QtWidgets.QApplication.clipboard()
|
||||||
clip.clear(mode=clip.Clipboard)
|
clip.clear(mode=clip.Clipboard)
|
||||||
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
|
||||||
@param slot: slot to connect
|
@param slot: slot to connect
|
||||||
|
|
||||||
@retval QAction*
|
@retval QAction*
|
||||||
"""
|
"""
|
||||||
action = QtWidgets.QAction(display, self.parent)
|
action = QtWidgets.QAction(display, self.parent)
|
||||||
action.setData(data)
|
action.setData(data)
|
||||||
@@ -100,11 +100,11 @@ 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
|
||||||
|
|
||||||
@yield QAction*
|
@yield QAction*
|
||||||
"""
|
"""
|
||||||
default_actions = (
|
default_actions = (
|
||||||
("Copy column", data, self.slot_copy_column),
|
("Copy column", data, self.slot_copy_column),
|
||||||
@@ -116,11 +116,11 @@ 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
|
||||||
|
|
||||||
@yield QAction*
|
@yield QAction*
|
||||||
"""
|
"""
|
||||||
function_actions = (("Rename function", data, self.slot_rename_function),)
|
function_actions = (("Rename function", data, self.slot_rename_function),)
|
||||||
|
|
||||||
@@ -133,15 +133,15 @@ 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
|
||||||
|
|
||||||
@param pos: TODO
|
@param pos: TODO
|
||||||
@param item: TODO
|
@param item: TODO
|
||||||
@param model_index: TODO
|
@param model_index: TODO
|
||||||
|
|
||||||
@retval QMenu*
|
@retval QMenu*
|
||||||
"""
|
"""
|
||||||
menu = QtWidgets.QMenu()
|
menu = QtWidgets.QMenu()
|
||||||
|
|
||||||
@@ -151,16 +151,16 @@ 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
|
||||||
|
|
||||||
@param pos: TODO
|
@param pos: TODO
|
||||||
@param item: TODO
|
@param item: TODO
|
||||||
@param model_index: TODO
|
@param model_index: TODO
|
||||||
|
|
||||||
@retval QMenu*
|
@retval QMenu*
|
||||||
"""
|
"""
|
||||||
menu = QtWidgets.QMenu()
|
menu = QtWidgets.QMenu()
|
||||||
|
|
||||||
@@ -170,43 +170,43 @@ 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
|
||||||
"""
|
"""
|
||||||
if menu:
|
if menu:
|
||||||
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
|
||||||
|
|
||||||
@param action: QAction*
|
@param action: QAction*
|
||||||
"""
|
"""
|
||||||
_, item, model_index = action.data()
|
_, item, model_index = action.data()
|
||||||
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
|
||||||
|
|
||||||
@param action: QAction*
|
@param action: QAction*
|
||||||
"""
|
"""
|
||||||
_, item, _ = action.data()
|
_, item, _ = action.data()
|
||||||
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
|
||||||
|
|
||||||
@param action: QAction*
|
@param action: QAction*
|
||||||
"""
|
"""
|
||||||
_, item, model_index = action.data()
|
_, item, model_index = action.data()
|
||||||
|
|
||||||
@@ -216,12 +216,12 @@ 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
|
||||||
|
|
||||||
@param pos: TODO
|
@param pos: TODO
|
||||||
"""
|
"""
|
||||||
model_index = self.indexAt(pos)
|
model_index = self.indexAt(pos)
|
||||||
|
|
||||||
@@ -243,9 +243,9 @@ 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*
|
||||||
"""
|
"""
|
||||||
if not model_index.isValid():
|
if not model_index.isValid():
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -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,10 +30,10 @@ 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
|
||||||
"""
|
"""
|
||||||
super(CapaExplorerIdaHooks, self).__init__()
|
super(CapaExplorerIdaHooks, self).__init__()
|
||||||
|
|
||||||
@@ -43,11 +43,11 @@ 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
|
||||||
|
|
||||||
@retval must be 0
|
@retval must be 0
|
||||||
"""
|
"""
|
||||||
self.process_action_handle = self.process_action_hooks.get(name, None)
|
self.process_action_handle = self.process_action_hooks.get(name, None)
|
||||||
|
|
||||||
@@ -66,10 +66,10 @@ 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
|
||||||
"""
|
"""
|
||||||
self.screen_ea_changed_hook(idaapi.get_current_widget(), curr_ea, prev_ea)
|
self.screen_ea_changed_hook(idaapi.get_current_widget(), curr_ea, prev_ea)
|
||||||
|
|
||||||
@@ -300,13 +300,13 @@ 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
|
||||||
|
|
||||||
@param meta: metadata cache
|
@param meta: metadata cache
|
||||||
@param post: indicates pre or post action
|
@param post: indicates pre or post action
|
||||||
"""
|
"""
|
||||||
location = idaapi.get_screen_ea()
|
location = idaapi.get_screen_ea()
|
||||||
if not location or not capa.ida.helpers.is_func_start(location):
|
if not location or not capa.ida.helpers.is_func_start(location):
|
||||||
@@ -322,12 +322,12 @@ 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
|
||||||
@param old_ea: source ea
|
@param old_ea: source ea
|
||||||
"""
|
"""
|
||||||
if not self.view_limit_results_by_function.isChecked():
|
if not self.view_limit_results_by_function.isChecked():
|
||||||
# ignore if checkbox not selected
|
# ignore if checkbox not selected
|
||||||
return
|
return
|
||||||
@@ -508,9 +508,9 @@ 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
|
||||||
"""
|
"""
|
||||||
self.ida_reset()
|
self.ida_reset()
|
||||||
|
|
||||||
@@ -518,21 +518,21 @@ 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*
|
||||||
|
|
||||||
@reference: https://stackoverflow.com/questions/21725119/why-wont-qtooltips-appear-on-qactions-within-a-qmenu
|
@reference: https://stackoverflow.com/questions/21725119/why-wont-qtooltips-appear-on-qactions-within-a-qmenu
|
||||||
"""
|
"""
|
||||||
QtWidgets.QToolTip.showText(
|
QtWidgets.QToolTip.showText(
|
||||||
QtGui.QCursor.pos(), action.toolTip(), self.view_menu_bar, self.view_menu_bar.actionGeometry(action)
|
QtGui.QCursor.pos(), action.toolTip(), self.view_menu_bar, self.view_menu_bar.actionGeometry(action)
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
||||||
"""
|
"""
|
||||||
match = ""
|
match = ""
|
||||||
if self.view_limit_results_by_function.isChecked():
|
if self.view_limit_results_by_function.isChecked():
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -16,15 +16,15 @@ import capa.engine
|
|||||||
|
|
||||||
def convert_statement_to_result_document(statement):
|
def convert_statement_to_result_document(statement):
|
||||||
"""
|
"""
|
||||||
"statement": {
|
"statement": {
|
||||||
"type": "or"
|
"type": "or"
|
||||||
},
|
},
|
||||||
|
|
||||||
"statement": {
|
"statement": {
|
||||||
"max": 9223372036854775808,
|
"max": 9223372036854775808,
|
||||||
"min": 2,
|
"min": 2,
|
||||||
"type": "range"
|
"type": "range"
|
||||||
},
|
},
|
||||||
"""
|
"""
|
||||||
statement_type = statement.name.lower()
|
statement_type = statement.name.lower()
|
||||||
result = {"type": statement_type}
|
result = {"type": statement_type}
|
||||||
@@ -47,28 +47,28 @@ def convert_statement_to_result_document(statement):
|
|||||||
|
|
||||||
def convert_feature_to_result_document(feature):
|
def convert_feature_to_result_document(feature):
|
||||||
"""
|
"""
|
||||||
"feature": {
|
"feature": {
|
||||||
"number": 6,
|
"number": 6,
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
|
|
||||||
"feature": {
|
"feature": {
|
||||||
"api": "ws2_32.WSASocket",
|
"api": "ws2_32.WSASocket",
|
||||||
"type": "api"
|
"type": "api"
|
||||||
},
|
},
|
||||||
|
|
||||||
"feature": {
|
"feature": {
|
||||||
"match": "create TCP socket",
|
"match": "create TCP socket",
|
||||||
"type": "match"
|
"type": "match"
|
||||||
},
|
},
|
||||||
|
|
||||||
"feature": {
|
"feature": {
|
||||||
"characteristic": [
|
"characteristic": [
|
||||||
"loop",
|
"loop",
|
||||||
true
|
true
|
||||||
],
|
],
|
||||||
"type": "characteristic"
|
"type": "characteristic"
|
||||||
},
|
},
|
||||||
"""
|
"""
|
||||||
result = {"type": feature.name, feature.name: feature.get_value_str()}
|
result = {"type": feature.name, feature.name: feature.get_value_str()}
|
||||||
if feature.description:
|
if feature.description:
|
||||||
@@ -80,15 +80,15 @@ def convert_feature_to_result_document(feature):
|
|||||||
|
|
||||||
def convert_node_to_result_document(node):
|
def convert_node_to_result_document(node):
|
||||||
"""
|
"""
|
||||||
"node": {
|
"node": {
|
||||||
"type": "statement",
|
"type": "statement",
|
||||||
"statement": { ... }
|
"statement": { ... }
|
||||||
},
|
},
|
||||||
|
|
||||||
"node": {
|
"node": {
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"feature": { ... }
|
"feature": { ... }
|
||||||
},
|
},
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(node, capa.engine.Statement):
|
if isinstance(node, capa.engine.Statement):
|
||||||
@@ -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")
|
||||||
|
|||||||
@@ -71,22 +71,22 @@ logger = logging.getLogger("capa.show-capabilities-by-function")
|
|||||||
|
|
||||||
def render_matches_by_function(doc):
|
def render_matches_by_function(doc):
|
||||||
"""
|
"""
|
||||||
like:
|
like:
|
||||||
|
|
||||||
function at 0x1000321a with 33 features:
|
function at 0x1000321a with 33 features:
|
||||||
- get hostname
|
- get hostname
|
||||||
- initialize Winsock library
|
- initialize Winsock library
|
||||||
function at 0x10003286 with 63 features:
|
function at 0x10003286 with 63 features:
|
||||||
- create thread
|
- create thread
|
||||||
- terminate thread
|
- terminate thread
|
||||||
function at 0x10003415 with 116 features:
|
function at 0x10003415 with 116 features:
|
||||||
- write file
|
- write file
|
||||||
- send data
|
- send data
|
||||||
- link function at runtime
|
- link function at runtime
|
||||||
- create HTTP request
|
- create HTTP request
|
||||||
- get common file path
|
- get common file path
|
||||||
- send HTTP request
|
- send HTTP request
|
||||||
- connect to HTTP server
|
- connect to HTTP server
|
||||||
"""
|
"""
|
||||||
ostream = rutils.StringIO()
|
ostream = rutils.StringIO()
|
||||||
|
|
||||||
|
|||||||
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