-
Notifications
You must be signed in to change notification settings - Fork 567
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
Atlas-64
wants to merge
15
commits into
mandiant:master
Choose a base branch
from
Atlas-64:comments-bookmarks-option
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 12 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
fe2a208
adding options for choosing b/w bookmarks & comments
Atlas-64 07fc637
trying to fix the lint issue
Atlas-64 94c9ada
trying to fix the lint issue
Atlas-64 e57ad9b
trying to fix the lint issue
Atlas-64 1ffa18c
adding short description to Changelog
Atlas-64 0491470
adding comment to bypass ruff hook
Atlas-64 e90b303
saved black hook formatting
Atlas-64 9e2984c
Merge branch 'master' into comments-bookmarks-option
Atlas-64 e7d67c1
seperating namespace and comment functionality within label_matches
Atlas-64 b7c5267
Merge branch 'comments-bookmarks-option' of https://github.com/Atlas-…
Atlas-64 8cff831
fixing flake8 linter violation
Atlas-64 c71e1bf
Merge branch 'master' into comments-bookmarks-option
Atlas-64 869c3bd
defining seperate namespace & comments functions
Atlas-64 acee34e
Merge branch 'comments-bookmarks-option' of https://github.com/Atlas-…
Atlas-64 f26a806
Merge branch 'master' into comments-bookmarks-option
Atlas-64 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 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 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 |
---|---|---|
|
@@ -78,35 +78,35 @@ def __init__( | |
self.attack = attack | ||
self.mbc = mbc | ||
|
||
def bookmark_functions(self): | ||
def bookmark_functions(self, bookmarks=False): | ||
"""create bookmarks for MITRE ATT&CK & MBC mappings""" | ||
if bookmarks: | ||
if self.attack == [] and self.mbc == []: | ||
return | ||
|
||
if self.attack == [] and self.mbc == []: | ||
return | ||
for key in self.matches.keys(): | ||
addr = toAddr(hex(key)) # type: ignore [name-defined] # noqa: F821 | ||
func = getFunctionContaining(addr) # type: ignore [name-defined] # noqa: F821 | ||
|
||
# bookmark & tag MITRE ATT&CK tactics & MBC @ function scope | ||
if func is not None: | ||
func_addr = func.getEntryPoint() | ||
|
||
for key in self.matches.keys(): | ||
addr = toAddr(hex(key)) # type: ignore [name-defined] # noqa: F821 | ||
func = getFunctionContaining(addr) # type: ignore [name-defined] # noqa: F821 | ||
|
||
# bookmark & tag MITRE ATT&CK tactics & MBC @ function scope | ||
if func is not None: | ||
func_addr = func.getEntryPoint() | ||
|
||
if self.attack != []: | ||
for item in self.attack: | ||
attack_txt = "" | ||
for part in item.get("parts", {}): | ||
attack_txt = attack_txt + part + Namespace.DELIMITER | ||
attack_txt = attack_txt + item.get("id", {}) | ||
add_bookmark(func_addr, attack_txt, "CapaExplorer::MITRE ATT&CK") | ||
|
||
if self.mbc != []: | ||
for item in self.mbc: | ||
mbc_txt = "" | ||
for part in item.get("parts", {}): | ||
mbc_txt = mbc_txt + part + Namespace.DELIMITER | ||
mbc_txt = mbc_txt + item.get("id", {}) | ||
add_bookmark(func_addr, mbc_txt, "CapaExplorer::MBC") | ||
if self.attack != []: | ||
for item in self.attack: | ||
attack_txt = "" | ||
for part in item.get("parts", {}): | ||
attack_txt = attack_txt + part + Namespace.DELIMITER | ||
attack_txt = attack_txt + item.get("id", {}) | ||
add_bookmark(func_addr, attack_txt, "CapaExplorer::MITRE ATT&CK") | ||
|
||
if self.mbc != []: | ||
for item in self.mbc: | ||
mbc_txt = "" | ||
for part in item.get("parts", {}): | ||
mbc_txt = mbc_txt + part + Namespace.DELIMITER | ||
mbc_txt = mbc_txt + item.get("id", {}) | ||
add_bookmark(func_addr, mbc_txt, "CapaExplorer::MBC") | ||
|
||
def set_plate_comment(self, ghidra_addr): | ||
"""set plate comments at matched functions""" | ||
|
@@ -137,84 +137,114 @@ def set_pre_comment(self, ghidra_addr, sub_type, description): | |
else: | ||
return | ||
|
||
def label_matches(self): | ||
def label_matches(self, namespace=False, comments=False): | ||
"""label findings at function scopes and comment on subscope matches""" | ||
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 | ||
|
||
# classify new function label under capa-generated namespace | ||
sym = symbol_table.getPrimarySymbol(ghidra_addr) | ||
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) | ||
if namespace: | ||
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 | ||
|
||
# classify new function label under capa-generated namespace | ||
sym = symbol_table.getPrimarySymbol(ghidra_addr) | ||
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 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 | ||
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 | ||
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: | ||
# 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() | ||
# 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) | ||
if func is not None: | ||
pass | ||
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): | ||
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) | ||
if comments: | ||
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 | ||
# 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: | ||
for addr in self.matches.keys(): | ||
ghidra_addr = toAddr(hex(addr)) # type: ignore [name-defined] # noqa: F821 | ||
func = getFunctionContaining(ghidra_addr) # type: ignore [name-defined] # noqa: F821 | ||
if func is not None: | ||
func_addr = func.getEntryPoint() | ||
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 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: | ||
for sub_type, description in parse_node(node): | ||
self.set_pre_comment(sub_ghidra_addr, sub_type, description) | ||
|
||
|
||
def get_capabilities(): | ||
|
@@ -359,9 +389,24 @@ def main(): | |
popup("capa explorer found no matches.") # type: ignore [name-defined] # noqa: F821 | ||
return capa.main.E_EMPTY_REPORT | ||
|
||
user_choices = askChoices( # type: ignore [name-defined] # noqa: F821 | ||
"Choose what is added to your database", | ||
"Items to add", | ||
["Namespace", "Comments", "Bookmarks"], | ||
) | ||
|
||
for choice in user_choices: | ||
if choice == "Namespace": | ||
namespace = True | ||
elif choice == "Comments": | ||
comments = True | ||
elif choice == "Bookmarks": | ||
bookmarks = True | ||
|
||
for item in parse_json(capa_data): | ||
item.bookmark_functions() | ||
item.label_matches() | ||
item.bookmark_functions(bookmarks) | ||
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. Let's simply not call |
||
item.label_matches(namespace, comments) | ||
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. See my comment above about splitting this function in two. |
||
|
||
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 | ||
|
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.
Let's split this into two functions,
create_capa_namespace
andcreate_capa_comments
, that are only called if the corresponding bool values retrieved from the user areTrue
.