Skip to content

Commit a39edd7

Browse files
Add Bash support to script analysis
1 parent 60bb1b1 commit a39edd7

3 files changed

Lines changed: 28 additions & 2 deletions

File tree

capa/features/extractors/ts/engine.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def get_global_statements(self) -> Iterator[Node]:
121121

122122

123123
class TreeSitterExtractorEngine(TreeSitterBaseEngine):
124-
query: ScriptQueryBinding
124+
query: ScriptQueryBinding | BashQueryBinding
125125
language_toolkit: LanguageToolkit
126126
namespaces: set[BaseNamespace]
127127

@@ -142,34 +142,47 @@ def __init__(
142142
self.namespaces = self.namespaces.union(additional_namespaces)
143143

144144
def get_new_object_names(self, node: Node) -> Iterator[Node]:
145+
if not isinstance(self.query, ScriptQueryBinding):
146+
return
145147
cursor = QueryCursor(self.query.new_object_name)
146148
yield from self.get_captured_nodes(cursor, node)
147149

148150
def get_property_names(self, node: Node) -> Iterator[Node]:
151+
if not isinstance(self.query, ScriptQueryBinding):
152+
return
149153
cursor = QueryCursor(self.query.property_name)
150154
yield from self.get_captured_nodes(cursor, node)
151155

152156
def get_processed_property_names(self, node: Node) -> Iterator[Tuple[Node, str]]:
153157
"""Generates captured property name nodes and their associated proper names (see process_property
154158
for details), e.g.: [(node0, "StartInfo"), (node1, "RedirectStandardOutput")]."""
159+
if not isinstance(self.query, ScriptQueryBinding):
160+
return
155161
for pt_node in self.get_property_names(node):
156162
pt_name = self.language_toolkit.process_property(pt_node, self.get_str(pt_node))
157163
if pt_name:
158164
yield pt_node, pt_name
159165

160166
def get_imported_constants(self, node: Node) -> Iterator[Node]:
167+
if not isinstance(self.query, ScriptQueryBinding):
168+
return
161169
cursor = QueryCursor(self.query.imported_constant_name)
162170
yield from self.get_captured_nodes(cursor, node)
163171

164172
def get_processed_imported_constants(self, node: Node) -> Iterator[Tuple[Node, str]]:
165173
"""Generates captured imported constant nodes and their associated proper names (see process_imported_constant
166174
for details), e.g.: [(node0, "ssl.CERT_NONE"), (node1, "win32con.FILE_ATTRIBUTE_HIDDEN")]."""
175+
if not isinstance(self.query, ScriptQueryBinding):
176+
return
167177
for ic_node in self.get_imported_constants(node):
168178
ic_name = self.language_toolkit.process_imported_constant(ic_node, self.get_str(ic_node))
169179
if ic_name:
170180
yield ic_node, ic_name
171181

172182
def get_namespaces(self, node: Optional[Node] = None) -> List[Tuple[Node, str]]:
183+
if not isinstance(self.query, ScriptQueryBinding):
184+
return []
185+
173186
target_node = self.tree.root_node if node is None else node
174187
cursor = QueryCursor(self.query.namespace)
175188
namespace_captures: List[Tuple[Node, str]] = []
@@ -181,10 +194,15 @@ def get_namespaces(self, node: Optional[Node] = None) -> List[Tuple[Node, str]]:
181194
return sorted(namespace_captures, key=self.get_node_capture_sort_key)
182195

183196
def get_processed_namespaces(self, node: Optional[Node] = None) -> Iterator[BaseNamespace]:
197+
if not isinstance(self.query, ScriptQueryBinding):
198+
return
184199
for ns_node, query_name in self.get_namespaces(node):
185200
yield from self.language_toolkit.process_namespace(ns_node, query_name, self.get_str)
186201

187202
def get_direct_method_call(self, node: Node) -> Optional[Node]:
203+
if not isinstance(self.query, ScriptQueryBinding):
204+
return None
205+
188206
cursor = QueryCursor(self.query.direct_method_call)
189207
captures = cursor.captures(node)
190208
for nodes in captures.values():

capa/features/extractors/ts/function.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from capa.features.insn import API, Number, Property
2222
from capa.features.common import Class, String, Feature, Namespace
2323
from capa.features.address import Address
24+
from capa.features.extractors.ts.query import BashQueryBinding
2425
from capa.features.extractors.ts.tools import BaseNamespace
2526
from capa.features.extractors.ts.engine import TreeSitterBaseEngine, TreeSitterBashEngine, TreeSitterExtractorEngine
2627
from capa.features.extractors.base_extractor import FunctionHandle
@@ -167,10 +168,18 @@ def _extract_function_calls(
167168
) -> Iterator[Tuple[Feature, Address]]:
168169
for node in engine.get_function_call_names(fn_node):
169170
yield from _extract_static_methods(node, engine)
171+
172+
if isinstance(engine.query, BashQueryBinding):
173+
continue
174+
170175
yield from _extract_instance_methods(node, classes, engine)
171176

172177

173178
def extract_imports(fn_node: Node, engine: TreeSitterExtractorEngine) -> Iterator[Tuple[Feature, Address]]:
179+
if isinstance(engine.query, BashQueryBinding):
180+
yield from _extract_function_calls(fn_node, set(), engine)
181+
return
182+
174183
classes = {engine.language_toolkit.create_namespace(cls) for cls in get_classes(fn_node, engine)}
175184
yield from _extract_classes(fn_node, engine)
176185
yield from _extract_constants(fn_node, engine)

tests/fixtures/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1223,7 +1223,6 @@ def get_ts_template_engine(path):
12231223
return capa.features.extractors.ts.engine.TreeSitterTemplateEngine(buf)
12241224

12251225

1226-
@functools.lru_cache(maxsize=1)
12271226
def get_ts_extractor(path):
12281227
import capa.features.extractors.ts.extractor
12291228

0 commit comments

Comments
 (0)