mandiant / mandiant/capa

tree-sitter: fit script constants and attributes into existing feature set

Open
#3,158 2 comments 0 reactions 1 assignee Claimed by @mike-hunhoff View on GitHub
enhancement gsoc question tree-sitter
Dominant language
Python
Stars
6.2k
Forks
726
Avg merge
11d 11h
Merged PRs (30d)
7

Description

Imported constants and environment/module attributes are currently extracted as `API` features formatted with `::` (e.g., `api(socket::AF_INET)`, `api(os::environ)`, `api(win32con::FILE_ATTRIBUTE_HIDDEN)`).

This causes a few issues for rule authoring:
1. **Misleading feature semantics**: `socket.AF_INET` and `os.environ` are not callable routines/APIs. Treating them as `api` confuses rule authors.
2. **Lost rule reuse**: Existing rules for native and .NET binaries already match on `number: 2 = AF_INET` or `property/read: ...`. Having script-specific `api(socket::AF_INET)` prevents existing rules from matching scripts without adding duplicate statements.
3. **No read vs. write distinction**: Module attributes like `os.environ` can be read or modified, but cannot match `property/read` or `property/write`.

---

## Proposed Solution

Fit script constants and attributes into capa's existing feature set without extending rule syntax:

### 1. Numeric & Enum Constants $\rightarrow$ `Number`
Map known enum/flag constants in signature files (`py.json`, `cs.json`, etc.) to their numeric values and emit them as `Number` with descriptions:

- `socket.AF_INET` $\rightarrow$ `number(0x2 = AF_INET)`
- `socket.SOCK_STREAM` $\rightarrow$ `number(0x1 = SOCK_STREAM)`
- `win32con.FILE_ATTRIBUTE_HIDDEN` $\rightarrow$ `number(0x2 = FILE_ATTRIBUTE_HIDDEN)`
- `subprocess.PIPE` $\rightarrow$ `number(-0x1 = PIPE)`

**Rule Example**:
Existing rules written for compiled binaries immediately match scripts:
```yaml
- and:
- api: socket.socket
- number: 2 = AF_INET
- number: 1 = SOCK_STREAM
```

### 2. Object & Environment Attributes $\rightarrow$ `Property`
Emit module/object attributes (like `os.environ`, `sys.executable`, `process.env`, `Process.StartInfo.FileName`) as `Property` using the standard `Scope::Member` convention (`os::environ`).

Use AST parent inspection to detect access mode:
- **`property/write`**: Property is the target (`left` child) of an assignment (e.g., `os.environ["appdata"] = "..."`, `p.StartInfo.FileName = "..."`).
- **`property/read`**: Property is read in an expression, condition, or function argument.
- **`property`**: Generic match for either access type.

**Rule Example**:
```yaml
- property/read: os::environ
- property/write: os::environ
```

---

## Benefits
- **Zero rule syntax changes**: Reuses existing `number`, `property`, `property/read`, and `property/write` keywords.
- **Consistent across languages**: Scales cleanly across Python, C#, PowerShell (`$env:APPDATA`), JavaScript (`process.env`), and native binaries.
- **Maximized rule reuse**: Rules looking for standard OS numbers/flags work across both compiled binaries and scripts out of the box.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.