Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

updates capa_explorer.py, enabling the user to choose b/w having bookmarks & comments. #2029

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Change Log

## master (unreleased)

- Emit "dotnet" as format to ResultDocument when processing .NET files #2024 @samadpls

### capa explorer user-option for choosing comments/bookmarks
- add function in capa explorer to let users choose if they want comments/bookmarks #1977 @Atlas-64

### New Features

- add function in capa/helpers to load plain and compressed JSON reports #1883 @Rohit1123
Expand Down
89 changes: 68 additions & 21 deletions capa/ghidra/capa_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ def __init__(

def bookmark_functions(self):
"""create bookmarks for MITRE ATT&CK & MBC mappings"""

if self.attack == [] and self.mbc == []:
return

Expand Down Expand Up @@ -137,13 +136,14 @@ def set_pre_comment(self, ghidra_addr, sub_type, description):
else:
return

def label_matches(self):
"""label findings at function scopes and comment on subscope matches"""
def create_capa_namespace(self):
"""label findings at function scopes"""
capa_namespace = create_namespace(self.namespace)
symbol_table = currentProgram().getSymbolTable() # type: ignore [name-defined] # noqa: F821

# handle function main scope of matched rule
# these will typically contain further matches within

if self.scope == "function":
for addr in self.matches.keys():
ghidra_addr = toAddr(hex(addr)) # type: ignore [name-defined] # noqa: F821
Expand All @@ -153,6 +153,52 @@ def label_matches(self):
if sym is not None:
if sym.getSymbolType() == SymbolType.FUNCTION:
create_label(ghidra_addr, sym.getName(), capa_namespace)

else:
# resolve the encompassing function for the capa namespace
# of non-function scoped main matches
for addr in self.matches.keys():
ghidra_addr = toAddr(hex(addr)) # type: ignore [name-defined] # noqa: F821

# basic block / insn scoped main matches
# Ex. See "Create Process on Windows" Rule
func = getFunctionContaining(ghidra_addr) # type: ignore [name-defined] # noqa: F821
if func is not None:
func_addr = func.getEnrtyPoint()
create_label(func_addr, func.getName(), capa_namespace)

for sub_match in self.matches.get(addr):
for loc, node in sub_match.items():
sub_ghidra_addr = toAddr(hex(loc)) # type: ignore [name-defined] # noqa: F821

if node != {}:
if func is not None:
pass
else:
sub_func = getFunctionContaining(sub_ghidra_addr) # type: ignore [name-defined] # noqa: F821
if sub_func is not None:
sub_func_addr = sub_func.getEntryPoint()
# place function in capa namespace & create the subscope match label in Ghidra's global namespace
create_label(sub_func_addr, sub_func.getName(), capa_namespace)
else:
# addr is in some other file section like .data
# represent this location with a label symbol under the capa namespace
# Ex. See "Reference Base64 String" rule
# in many cases, these will be ghidra-labeled data, so just add the existing
# label symbol to the capa namespace
for sym in symbol_table.getSymbols(sub_ghidra_addr):
if sym.getSymbolType() == SymbolType.LABEL:
sym.setNamespace(capa_namespace)

def create_capa_comments(self):
"""comment on subscope matches"""
symbol_table = currentProgram().getSymbolTable() # type: ignore [name-defined] # noqa: F821
if self.scope == "function":
for addr in self.matches.keys():
ghidra_addr = toAddr(hex(addr)) # type: ignore [name-defined] # noqa: F821
sym = symbol_table.getPrimarySymbol(ghidra_addr)
if sym is not None:
if sym.getSymbolType() == SymbolType.FUNCTION:
self.set_plate_comment(ghidra_addr)

# parse the corresponding nodes, and pre-comment subscope matched features
Expand All @@ -169,17 +215,11 @@ def label_matches(self):
for sub_type, description in parse_node(node):
self.set_pre_comment(sub_ghidra_addr, sub_type, description)
else:
# resolve the encompassing function for the capa namespace
# of non-function scoped main matches
for addr in self.matches.keys():
ghidra_addr = toAddr(hex(addr)) # type: ignore [name-defined] # noqa: F821

# basic block / insn scoped main matches
# Ex. See "Create Process on Windows" Rule
func = getFunctionContaining(ghidra_addr) # type: ignore [name-defined] # noqa: F821
if func is not None:
func_addr = func.getEntryPoint()
create_label(func_addr, func.getName(), capa_namespace)
self.set_plate_comment(func_addr)

# create subscope match precomments
Expand All @@ -199,21 +239,11 @@ def label_matches(self):
sub_func = getFunctionContaining(sub_ghidra_addr) # type: ignore [name-defined] # noqa: F821
if sub_func is not None:
sub_func_addr = sub_func.getEntryPoint()
# place function in capa namespace & create the subscope match label in Ghidra's global namespace
create_label(sub_func_addr, sub_func.getName(), capa_namespace)
self.set_plate_comment(sub_func_addr)
for sub_type, description in parse_node(node):
self.set_pre_comment(sub_ghidra_addr, sub_type, description)
else:
# addr is in some other file section like .data
# represent this location with a label symbol under the capa namespace
# Ex. See "Reference Base64 String" rule
for sub_type, description in parse_node(node):
# in many cases, these will be ghidra-labeled data, so just add the existing
# label symbol to the capa namespace
for sym in symbol_table.getSymbols(sub_ghidra_addr):
if sym.getSymbolType() == SymbolType.LABEL:
sym.setNamespace(capa_namespace)
self.set_pre_comment(sub_ghidra_addr, sub_type, description)


Expand Down Expand Up @@ -359,9 +389,26 @@ def main():
popup("capa explorer found no matches.") # type: ignore [name-defined] # noqa: F821
return capa.main.E_EMPTY_REPORT

# dialog box for user input
user_choices = askChoices( # type: ignore [name-defined] # noqa: F821
"Choose what is added to your database",
"Items to add",
["Namespace", "Comments", "Bookmarks"],
)

# intializes the booleans
namespace = "Namespace" in user_choices
comments = "Comments" in user_choices
bookmarks = "Bookmarks" in user_choices

for item in parse_json(capa_data):
item.bookmark_functions()
item.label_matches()
if namespace:
item.create_capa_namespace()
if comments:
item.create_capa_comments()
if bookmarks:
item.bookmark_functions()

logger.info("capa explorer analysis complete")
popup("capa explorer analysis complete.\nPlease see results in the Bookmarks Window and Namespaces section of the Symbol Tree Window.") # type: ignore [name-defined] # noqa: F821
return 0
Expand Down