From fe2a20884d4e730ff89109b3c8aa586677c39e00 Mon Sep 17 00:00:00 2001 From: Pratik Mohapatra Date: Thu, 14 Mar 2024 13:25:21 +0530 Subject: [PATCH 01/10] adding options for choosing b/w bookmarks & comments --- capa/ghidra/capa_explorer.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/capa/ghidra/capa_explorer.py b/capa/ghidra/capa_explorer.py index e0b2cf19a..9ac071a55 100644 --- a/capa/ghidra/capa_explorer.py +++ b/capa/ghidra/capa_explorer.py @@ -359,9 +359,23 @@ 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() + user_choice = askChoice( # type: ignore [name-defined] # noqa: F821 + "Choose b/w bookmarks & comments", "preferred action:", ["bookmarks", "comments", "both", "none"], "both") + + + if user_choice == "bookmarks": + for item in parse_json(capa_data): + item.bookmark_functions() + elif user_choice == "comments": + for item in parse_json(capa_data): + item.label_matches() + elif user_choice == "both": + for item in parse_json(capa_data): + item.bookmark_functions() + item.label_matches() + else: + pass + 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 From 07fc637421d118a7d525e630a7c5bb99a172399f Mon Sep 17 00:00:00 2001 From: Pratik Mohapatra Date: Thu, 14 Mar 2024 15:55:34 +0530 Subject: [PATCH 02/10] trying to fix the lint issue --- capa/ghidra/capa_explorer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/capa/ghidra/capa_explorer.py b/capa/ghidra/capa_explorer.py index 9ac071a55..0edb8f825 100644 --- a/capa/ghidra/capa_explorer.py +++ b/capa/ghidra/capa_explorer.py @@ -362,7 +362,6 @@ def main(): user_choice = askChoice( # type: ignore [name-defined] # noqa: F821 "Choose b/w bookmarks & comments", "preferred action:", ["bookmarks", "comments", "both", "none"], "both") - if user_choice == "bookmarks": for item in parse_json(capa_data): item.bookmark_functions() From 94c9ada946c97a724d90b15506d31a120c97269d Mon Sep 17 00:00:00 2001 From: Pratik Mohapatra Date: Thu, 14 Mar 2024 16:07:15 +0530 Subject: [PATCH 03/10] trying to fix the lint issue --- capa/ghidra/capa_explorer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/capa/ghidra/capa_explorer.py b/capa/ghidra/capa_explorer.py index 0edb8f825..0eb22e4ad 100644 --- a/capa/ghidra/capa_explorer.py +++ b/capa/ghidra/capa_explorer.py @@ -360,8 +360,9 @@ def main(): return capa.main.E_EMPTY_REPORT user_choice = askChoice( # type: ignore [name-defined] # noqa: F821 - "Choose b/w bookmarks & comments", "preferred action:", ["bookmarks", "comments", "both", "none"], "both") - + "Choose b/w bookmarks & comments", "preferred action:", ["bookmarks", "comments", "both", "none"], "both" + ) + if user_choice == "bookmarks": for item in parse_json(capa_data): item.bookmark_functions() From e57ad9b1f40465dbe881c8e27efc36bd043bb6f3 Mon Sep 17 00:00:00 2001 From: Pratik Mohapatra Date: Thu, 14 Mar 2024 16:18:41 +0530 Subject: [PATCH 04/10] trying to fix the lint issue --- capa/ghidra/capa_explorer.py | 77 ++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/capa/ghidra/capa_explorer.py b/capa/ghidra/capa_explorer.py index 0eb22e4ad..dc8066309 100644 --- a/capa/ghidra/capa_explorer.py +++ b/capa/ghidra/capa_explorer.py @@ -48,7 +48,10 @@ def create_label(ghidra_addr, name, capa_namespace): # prevent duplicate labels under the same capa-generated namespace symbol_table = currentProgram().getSymbolTable() # type: ignore [name-defined] # noqa: F821 for sym in symbol_table.getSymbols(ghidra_addr): - if sym.getName(True) == capa_namespace.getName(True) + Namespace.DELIMITER + name: + if ( + sym.getName(True) + == capa_namespace.getName(True) + Namespace.DELIMITER + name + ): return # create SymbolType.LABEL at addr @@ -98,7 +101,9 @@ def bookmark_functions(self): 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") + add_bookmark( + func_addr, attack_txt, "CapaExplorer::MITRE ATT&CK" + ) if self.mbc != []: for item in self.mbc: @@ -127,11 +132,28 @@ def set_pre_comment(self, ghidra_addr, sub_type, description): """set pre comments at subscoped matches of main rules""" comment = getPreComment(ghidra_addr) # type: ignore [name-defined] # noqa: F821 if comment is None: - comment = "capa: " + sub_type + "(" + description + ")" + ' matched in "' + self.capability + '"\n' + comment = ( + "capa: " + + sub_type + + "(" + + description + + ")" + + ' matched in "' + + self.capability + + '"\n' + ) setPreComment(ghidra_addr, comment) # type: ignore [name-defined] # noqa: F821 elif self.capability not in comment: comment = ( - comment + "capa: " + sub_type + "(" + description + ")" + ' matched in "' + self.capability + '"\n' + comment + + "capa: " + + sub_type + + "(" + + description + + ")" + + ' matched in "' + + self.capability + + '"\n' ) setPreComment(ghidra_addr, comment) # type: ignore [name-defined] # noqa: F821 else: @@ -167,7 +189,9 @@ def label_matches(self): # 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) + 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 @@ -191,7 +215,9 @@ def label_matches(self): 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) + 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 @@ -200,10 +226,16 @@ def label_matches(self): 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) + 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) + 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 @@ -211,10 +243,14 @@ def label_matches(self): 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): + 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) + self.set_pre_comment( + sub_ghidra_addr, sub_type, description + ) def get_capabilities(): @@ -238,9 +274,13 @@ def get_capabilities(): meta = capa.ghidra.helpers.collect_metadata([rules_path]) extractor = capa.features.extractors.ghidra.extractor.GhidraFeatureExtractor() - capabilities, counts = capa.capabilities.common.find_capabilities(rules, extractor, True) + capabilities, counts = capa.capabilities.common.find_capabilities( + rules, extractor, True + ) - if capa.capabilities.common.has_file_limitation(rules, capabilities, is_standalone=False): + if capa.capabilities.common.has_file_limitation( + rules, capabilities, is_standalone=False + ): popup("capa explorer encountered warnings during analysis. Please check the console output for more information.") # type: ignore [name-defined] # noqa: F821 logger.info("capa encountered warnings during analysis") @@ -360,9 +400,12 @@ def main(): return capa.main.E_EMPTY_REPORT user_choice = askChoice( # type: ignore [name-defined] # noqa: F821 - "Choose b/w bookmarks & comments", "preferred action:", ["bookmarks", "comments", "both", "none"], "both" + "Choose b/w bookmarks & comments", + "preferred action:", + ["bookmarks", "comments", "both", "none"], + "both", ) - + if user_choice == "bookmarks": for item in parse_json(capa_data): item.bookmark_functions() @@ -375,7 +418,7 @@ def main(): item.label_matches() else: pass - + 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 @@ -385,7 +428,9 @@ def main(): if sys.version_info < (3, 8): from capa.exceptions import UnsupportedRuntimeError - raise UnsupportedRuntimeError("This version of capa can only be used with Python 3.8+") + raise UnsupportedRuntimeError( + "This version of capa can only be used with Python 3.8+" + ) exit_code = main() if exit_code != 0: popup("capa explorer encountered errors during analysis. Please check the console output for more information.") # type: ignore [name-defined] # noqa: F821 From 1ffa18c79133ffe482a5e7685d6754dedaef48b5 Mon Sep 17 00:00:00 2001 From: Pratik Mohapatra Date: Tue, 19 Mar 2024 17:06:59 +0530 Subject: [PATCH 05/10] adding short description to Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9729e9b02..4633c08a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Change Log ## master (unreleased) +### 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 From 0491470002a4f2a8f3b256829d4a65e3bc97bb50 Mon Sep 17 00:00:00 2001 From: Pratik Mohapatra Date: Tue, 19 Mar 2024 18:30:35 +0530 Subject: [PATCH 06/10] adding comment to bypass ruff hook --- capa/ghidra/capa_explorer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/capa/ghidra/capa_explorer.py b/capa/ghidra/capa_explorer.py index dc8066309..725fb8169 100644 --- a/capa/ghidra/capa_explorer.py +++ b/capa/ghidra/capa_explorer.py @@ -399,6 +399,7 @@ def main(): popup("capa explorer found no matches.") # type: ignore [name-defined] # noqa: F821 return capa.main.E_EMPTY_REPORT + user_choice = askChoice( # type: ignore [name-defined] # noqa: F821 "Choose b/w bookmarks & comments", "preferred action:", From e90b303465d3f05ee74a7a44c4fabcfd055e1cd8 Mon Sep 17 00:00:00 2001 From: Pratik Mohapatra Date: Tue, 19 Mar 2024 18:37:53 +0530 Subject: [PATCH 07/10] saved black hook formatting --- capa/ghidra/capa_explorer.py | 63 +++++++----------------------------- 1 file changed, 12 insertions(+), 51 deletions(-) diff --git a/capa/ghidra/capa_explorer.py b/capa/ghidra/capa_explorer.py index 725fb8169..1d5dbcc6e 100644 --- a/capa/ghidra/capa_explorer.py +++ b/capa/ghidra/capa_explorer.py @@ -48,10 +48,7 @@ def create_label(ghidra_addr, name, capa_namespace): # prevent duplicate labels under the same capa-generated namespace symbol_table = currentProgram().getSymbolTable() # type: ignore [name-defined] # noqa: F821 for sym in symbol_table.getSymbols(ghidra_addr): - if ( - sym.getName(True) - == capa_namespace.getName(True) + Namespace.DELIMITER + name - ): + if sym.getName(True) == capa_namespace.getName(True) + Namespace.DELIMITER + name: return # create SymbolType.LABEL at addr @@ -101,9 +98,7 @@ def bookmark_functions(self): 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" - ) + add_bookmark(func_addr, attack_txt, "CapaExplorer::MITRE ATT&CK") if self.mbc != []: for item in self.mbc: @@ -132,28 +127,11 @@ def set_pre_comment(self, ghidra_addr, sub_type, description): """set pre comments at subscoped matches of main rules""" comment = getPreComment(ghidra_addr) # type: ignore [name-defined] # noqa: F821 if comment is None: - comment = ( - "capa: " - + sub_type - + "(" - + description - + ")" - + ' matched in "' - + self.capability - + '"\n' - ) + comment = "capa: " + sub_type + "(" + description + ")" + ' matched in "' + self.capability + '"\n' setPreComment(ghidra_addr, comment) # type: ignore [name-defined] # noqa: F821 elif self.capability not in comment: comment = ( - comment - + "capa: " - + sub_type - + "(" - + description - + ")" - + ' matched in "' - + self.capability - + '"\n' + comment + "capa: " + sub_type + "(" + description + ")" + ' matched in "' + self.capability + '"\n' ) setPreComment(ghidra_addr, comment) # type: ignore [name-defined] # noqa: F821 else: @@ -189,9 +167,7 @@ def label_matches(self): # 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 - ) + 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 @@ -215,9 +191,7 @@ def label_matches(self): 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 - ) + 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 @@ -233,9 +207,7 @@ def label_matches(self): ) 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 - ) + 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 @@ -243,14 +215,10 @@ def label_matches(self): 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 - ): + 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 - ) + self.set_pre_comment(sub_ghidra_addr, sub_type, description) def get_capabilities(): @@ -274,13 +242,9 @@ def get_capabilities(): meta = capa.ghidra.helpers.collect_metadata([rules_path]) extractor = capa.features.extractors.ghidra.extractor.GhidraFeatureExtractor() - capabilities, counts = capa.capabilities.common.find_capabilities( - rules, extractor, True - ) + capabilities, counts = capa.capabilities.common.find_capabilities(rules, extractor, True) - if capa.capabilities.common.has_file_limitation( - rules, capabilities, is_standalone=False - ): + if capa.capabilities.common.has_file_limitation(rules, capabilities, is_standalone=False): popup("capa explorer encountered warnings during analysis. Please check the console output for more information.") # type: ignore [name-defined] # noqa: F821 logger.info("capa encountered warnings during analysis") @@ -399,7 +363,6 @@ def main(): popup("capa explorer found no matches.") # type: ignore [name-defined] # noqa: F821 return capa.main.E_EMPTY_REPORT - user_choice = askChoice( # type: ignore [name-defined] # noqa: F821 "Choose b/w bookmarks & comments", "preferred action:", @@ -429,9 +392,7 @@ def main(): if sys.version_info < (3, 8): from capa.exceptions import UnsupportedRuntimeError - raise UnsupportedRuntimeError( - "This version of capa can only be used with Python 3.8+" - ) + raise UnsupportedRuntimeError("This version of capa can only be used with Python 3.8+") exit_code = main() if exit_code != 0: popup("capa explorer encountered errors during analysis. Please check the console output for more information.") # type: ignore [name-defined] # noqa: F821 From e7d67c1de83b6be0ae27abba965d32c13ec69b36 Mon Sep 17 00:00:00 2001 From: Pratik Mohapatra Date: Sat, 23 Mar 2024 12:19:03 +0530 Subject: [PATCH 08/10] seperating namespace and comment functionality within label_matches --- capa/ghidra/capa_explorer.py | 255 +++++++++++++++++++---------------- 1 file changed, 140 insertions(+), 115 deletions(-) diff --git a/capa/ghidra/capa_explorer.py b/capa/ghidra/capa_explorer.py index 1d5dbcc6e..45c310193 100644 --- a/capa/ghidra/capa_explorer.py +++ b/capa/ghidra/capa_explorer.py @@ -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 - 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") + # 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") def set_plate_comment(self, ghidra_addr): """set plate comments at matched functions""" @@ -137,88 +137,115 @@ 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) + 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 + 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) + 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: - # 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 + 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): - # 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) + 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(): @@ -363,25 +390,23 @@ def main(): popup("capa explorer found no matches.") # type: ignore [name-defined] # noqa: F821 return capa.main.E_EMPTY_REPORT - user_choice = askChoice( # type: ignore [name-defined] # noqa: F821 - "Choose b/w bookmarks & comments", - "preferred action:", - ["bookmarks", "comments", "both", "none"], - "both", + user_choices = askChoices( # type: ignore [name-defined] # noqa: F821 + "Choose what is added to your database", + "Items to add", + ["Namespace", "Comments", "Bookmarks"], ) - if user_choice == "bookmarks": - for item in parse_json(capa_data): - item.bookmark_functions() - elif user_choice == "comments": - for item in parse_json(capa_data): - item.label_matches() - elif user_choice == "both": - for item in parse_json(capa_data): - item.bookmark_functions() - item.label_matches() - else: - pass + 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(bookmarks) + item.label_matches(namespace, comments) 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 From 8cff83103187cf44df8549adbd2db9f65377e269 Mon Sep 17 00:00:00 2001 From: Pratik Mohapatra Date: Sat, 23 Mar 2024 12:44:53 +0530 Subject: [PATCH 09/10] fixing flake8 linter violation --- capa/ghidra/capa_explorer.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/capa/ghidra/capa_explorer.py b/capa/ghidra/capa_explorer.py index 45c310193..afd90e504 100644 --- a/capa/ghidra/capa_explorer.py +++ b/capa/ghidra/capa_explorer.py @@ -186,12 +186,11 @@ def label_matches(self, namespace=False, comments=False): # 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) + # 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": From 869c3bdb2fa2894b38a2a319c2c95d1d2e44e9d1 Mon Sep 17 00:00:00 2001 From: Pratik Mohapatra Date: Wed, 27 Mar 2024 23:18:25 +0530 Subject: [PATCH 10/10] defining seperate namespace & comments functions --- capa/ghidra/capa_explorer.py | 268 ++++++++++++++++++----------------- 1 file changed, 135 insertions(+), 133 deletions(-) diff --git a/capa/ghidra/capa_explorer.py b/capa/ghidra/capa_explorer.py index afd90e504..0ddef533b 100644 --- a/capa/ghidra/capa_explorer.py +++ b/capa/ghidra/capa_explorer.py @@ -78,35 +78,34 @@ def __init__( self.attack = attack self.mbc = mbc - def bookmark_functions(self, bookmarks=False): + def bookmark_functions(self): """create bookmarks for MITRE ATT&CK & MBC mappings""" - if bookmarks: - 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() + if self.attack == [] and self.mbc == []: + return - 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") + 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") def set_plate_comment(self, ghidra_addr): """set plate comments at matched functions""" @@ -137,114 +136,115 @@ def set_pre_comment(self, ghidra_addr, sub_type, description): else: return - def label_matches(self, namespace=False, comments=False): - """label findings at function scopes and comment on subscope matches""" - 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) + 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 - 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 + # handle function main scope of matched rule + # these will typically contain further matches within - if node != {}: - if func is not None: - pass + 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 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: - 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 + # 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 + # 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 != {}: - 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: + 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: - # 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) + for sub_type, description in parse_node(node): + self.set_pre_comment(sub_ghidra_addr, sub_type, description) def get_capabilities(): @@ -389,23 +389,25 @@ 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"], ) - for choice in user_choices: - if choice == "Namespace": - namespace = True - elif choice == "Comments": - comments = True - elif choice == "Bookmarks": - bookmarks = True + # 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(bookmarks) - item.label_matches(namespace, comments) + 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