-
Notifications
You must be signed in to change notification settings - Fork 619
ghidra: Updates capa_explorer.py to enable users to select if namespaces, comments and bookmarks are added. Closes #1977 #2652
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
Open
Shajal-Kumar
wants to merge
4
commits into
mandiant:master
Choose a base branch
from
Shajal-Kumar:solve-issue1977
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,13 +77,19 @@ def __init__( | |
matches, | ||
attack: list[dict[Any, Any]], | ||
mbc: list[dict[Any, Any]], | ||
do_labels: bool, | ||
do_comments: bool, | ||
do_bookmarks: bool, | ||
): | ||
self.namespace = namespace | ||
self.scope = scope | ||
self.capability = capability | ||
self.matches = matches | ||
self.attack = attack | ||
self.mbc = mbc | ||
self.do_labels = do_labels | ||
self.do_comments = do_comments | ||
self.do_bookmarks = do_bookmarks | ||
|
||
def bookmark_functions(self): | ||
"""create bookmarks for MITRE ATT&CK & MBC mappings""" | ||
|
@@ -144,8 +150,11 @@ 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): | ||
"""create namespace and labels for matched rules""" | ||
if not self.do_labels: | ||
return | ||
|
||
capa_namespace = create_namespace(self.namespace) | ||
symbol_table = currentProgram().getSymbolTable() # type: ignore [name-defined] # noqa: F821 | ||
|
||
|
@@ -160,21 +169,7 @@ def label_matches(self): | |
if sym is not None: | ||
if sym.getSymbolType() == SymbolType.FUNCTION: | ||
create_label(ghidra_addr, sym.getName(), capa_namespace) | ||
self.set_plate_comment(ghidra_addr) | ||
|
||
# parse the corresponding nodes, and pre-comment subscope matched features | ||
# under the encompassing function(s) | ||
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 sub_ghidra_addr == ghidra_addr: | ||
# skip duplicates | ||
continue | ||
|
||
# precomment subscope matches under the function | ||
if node != {}: | ||
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 | ||
|
@@ -187,41 +182,75 @@ def label_matches(self): | |
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 | ||
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 func is not None: | ||
# basic block/ insn scope under resolved function | ||
# this would be a global/file scoped main match | ||
# try to resolve the encompassing function via the subscope match, instead | ||
# Ex. "run as service" rule | ||
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): | ||
"""create comments for matched rules""" | ||
if not self.do_comments: | ||
return | ||
|
||
if node != {}: | ||
if func is not None: | ||
# basic block/ insn scope under resolved function | ||
# create plate comments for the main rule match | ||
for addr in self.matches.keys(): | ||
ghidra_addr = toAddr(hex(addr)) # type: ignore [name-defined] # noqa: F821 | ||
if self.scope == "function": | ||
self.set_plate_comment(ghidra_addr) | ||
else: | ||
func = getFunctionContaining(ghidra_addr) # type: ignore [name-defined] # noqa: F821 | ||
if func is not None: | ||
self.set_plate_comment(func.getEntryPoint()) | ||
|
||
# create pre comments for subscoped matches of main rules | ||
for addr in self.matches.keys(): | ||
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 | ||
func = getFunctionContaining(sub_ghidra_addr) # type: ignore [name-defined] # noqa: F821 | ||
|
||
if node != {}: | ||
if func is not None: | ||
# basic block / insn scope under resolved function | ||
for sub_type, description in parse_node(node): | ||
self.set_pre_comment(sub_ghidra_addr, sub_type, description) | ||
else: | ||
# this would be a global/file scoped main match | ||
# try to resolve the encompassing function via the subscope match, instead | ||
# Ex. "run as service" rule | ||
sub_func = getFunctionContaining(sub_ghidra_addr) # type: ignore [name-defined] # noqa: F821 | ||
if sub_func is not None: | ||
sub_func_addr = sub_func.getEntryPoint() | ||
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: | ||
# this would be a global/file scoped main match | ||
# try to resolve the encompassing function via the subscope match, instead | ||
# Ex. "run as service" rule | ||
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) | ||
# 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 sub_type, description in parse_node(node): | ||
self.set_pre_comment(sub_ghidra_addr, sub_type, description) | ||
Comment on lines
+231
to
+253
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
+232
to
+253
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
|
||
def get_capabilities(): | ||
|
@@ -283,7 +312,7 @@ def parse_node(node_data): | |
yield feat_type, data | ||
|
||
|
||
def parse_json(capa_data): | ||
def parse_json(capa_data, do_labels, do_comments, do_bookmarks): | ||
"""Parse json produced by capa""" | ||
|
||
for rule, capability in capa_data.get("rules", {}).items(): | ||
|
@@ -335,7 +364,7 @@ def parse_json(capa_data): | |
# Ex. 'contain loop' -> capa::lib::contain-loop | ||
namespace = "capa" + Namespace.DELIMITER + "lib" + fmt_rule | ||
|
||
yield CapaMatchData(namespace, scope, rule, rule_matches, attack, mbc) | ||
yield CapaMatchData(namespace, scope, rule, rule_matches, attack, mbc, do_labels, do_comments, do_bookmarks) | ||
|
||
|
||
def main(): | ||
|
@@ -366,9 +395,24 @@ def main(): | |
popup("capa explorer found no matches.") # type: ignore [name-defined] # noqa: F821 | ||
return capa.main.E_EMPTY_REPORT | ||
|
||
for item in parse_json(capa_data): | ||
item.bookmark_functions() | ||
item.label_matches() | ||
options = ["Add Labels/Namespace", "Add Comments", "Add Bookmarks"] | ||
selected_options = askChoices("Capa Explorer Options", "Select options for capa analysis", options, options) # type: ignore [name-defined] # noqa: F821 | ||
|
||
do_labels = "Add Labels/Namespace" in selected_options | ||
do_comments = "Add Comments" in selected_options | ||
do_bookmarks = "Add Bookmarks" in selected_options | ||
|
||
if not any([do_bookmarks, do_comments, do_labels]): | ||
logger.info("No annotations selected") | ||
return 0 | ||
|
||
for item in parse_json(capa_data, do_labels, do_comments, do_bookmarks): | ||
if do_labels: | ||
item.create_capa_namespace() | ||
if do_comments: | ||
item.create_capa_comments() | ||
if do_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 | ||
|
Submodule rules
updated
50 files
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nested
if func is not None
andif sub_func is not None
can be simplified by using guard clauses or combining the conditions. This would improve readability.