summaryrefslogtreecommitdiff
path: root/docs/dev/containertransforms.md
blob: a7bcd5f9291cbd63d1aec96c70867fa706f7752e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# Container Transforms

Container Transforms are specialized transforms that enable Binary Ninja to extract and navigate files within container formats such as ZIP archives, disk images, and other multi-file structures. Unlike simple encoding transforms (Base64, Hex, etc.), container transforms can produce multiple output files and interact with the Container Browser UI.

You can list all available container transforms (those with detection support) using:

```python
>>> [x.name for x in Transform if getattr(x, "supports_detection", False)]
['Gzip', 'Zlib', 'Bzip2', 'LZMA', 'LZ4Frame', 'Zstd', 'XZ', 'Zip', 'CaRT', 'Tar', 'AR', 'CPIO', 'DMG', 'UImage', 'FIT', 'TRX', 'IntelHex', 'SRec', 'TiTxt', 'IMG4', 'LZFSE', 'Universal']
```

## Overview

The Transform API provides the foundation for creating custom container decoders. Container transforms differ from standard transforms in that they:

1. Support **context-aware decoding** via `perform_decode_with_context()`
2. Can produce **multiple output files** from a single input
3. Support **password protection** and other interactive parameters
4. Integrate with the **Container Browser** UI for file selection

## Basic Transform Structure

All transforms, including container transforms, inherit from the `Transform` base class. Here's a minimal example:

```python
from binaryninja import Transform, TransformType, TransformCapabilities

class MyContainerTransform(Transform):
    transform_type = TransformType.DecodeTransform
    capabilities = TransformCapabilities.TransformSupportsContext | TransformCapabilities.TransformSupportsDetection
    name = "MyContainer"
    long_name = "My Container Format"
    group = "Container"

    def can_decode(self, input):
        """Check if this transform can decode the input"""
        # Check for magic bytes or other signatures
        head = input.read(0, 4)
        return head == b"MYCN"  # Your format's magic bytes

    def perform_decode_with_context(self, context, params):
        """Context-aware extraction for multi-file containers"""
        # Implementation details below
        pass

# Register the transform
MyContainerTransform.register()
```

## Container Extraction Protocol

Container transforms typically operate in **two phases**:

### Phase 1: Discovery

During discovery, the transform enumerates all available files and populates `context.available_files`:

```python
def perform_decode_with_context(self, context, params):
    # Parse the container format
    container = parse_my_format(context.input)

    # Phase 1: Discovery
    if not context.has_available_files:
        file_list = [entry.name for entry in container.entries]
        context.set_available_files(file_list)
        return False  # More user interaction needed
```

Returning `False` indicates that the Container Browser should present these files to the user for selection.

### Phase 2: Extraction

Once the user selects files, the transform extracts them and creates child contexts:

```python
def perform_decode_with_context(self, context, params):
    container = parse_my_format(context.input)

    # Phase 1: Discovery (as above)
    if not context.has_available_files:
        # ... discovery code ...
        return False

    # Phase 2: Extraction
    requested = context.requested_files
    if not requested:
        return False  # No files selected yet

    complete = True
    for filename in requested:
        try:
            data = container.extract(filename)
            context.create_child(DataBuffer(data), filename)
        except Exception as e:
            # Create child with error status
            context.create_child(
                DataBuffer(b""),
                filename,
                result=TransformResult.TransformFailure,
                message=str(e)
            )
            complete = False

    return complete  # True if all files extracted successfully
```

## Complete Example: ZipPython

Binary Ninja includes a reference implementation of a ZIP container transform in `api/python/transform.py`.



## Transform Results and Error Handling

Use `TransformResult` values to communicate extraction status:

- `TransformResult.TransformSuccess`: Extraction completed successfully
- `TransformResult.TransformNotAttempted`: Extraction not attempted
- `TransformResult.TransformFailure`: Generic extraction failure
- `TransformResult.TransformRequiresPassword`: File is encrypted and needs a password

Set results on individual child contexts:

```python
context.create_child(
    data=databuffer.DataBuffer(extracted_data),
    filename="file.bin",
    result=TransformResult.TransformSuccess,
    message=""  # Optional success message
)
```

## Working with Passwords

Container transforms should integrate with Binary Ninja's password management system:

```python
# Get passwords from settings
passwords = Settings().get_string_list('files.container.defaultPasswords')

# Check for password in transform parameters
if "password" in params:
    p = params["password"]
    pwd = p.decode("utf-8", "replace") if isinstance(p, (bytes, bytearray)) else str(p)
    passwords.insert(0, pwd)

# Try each password
for password in passwords:
    try:
        content = extract_with_password(container, filename, password)
        break  # Success!
    except PasswordError:
        continue  # Try next password
```

When a file requires a password that wasn't provided, use `TransformResult.TransformRequiresPassword` to signal the UI to prompt the user.

## Metadata and Virtual Paths

Container transforms automatically create metadata that tracks the extraction chain:

```python
# After opening a file extracted through containers:
>>> bv.parent_view.auto_metadata['container']
{
    'chain': [
        {'transform': 'Zip'},
        {'transform': 'Base64'}
    ],
    'virtualPath': 'Zip(/path/to/archive.zip)::Base64(encoded_file)::extracted'
}
```

You can also add custom metadata to child contexts:

```python
child = context.create_child(data, filename)
if child.metadata_obj:
    child.metadata_obj["custom_field"] = "value"
```

## Testing Container Transforms

When testing your container transform, you can use the Python API directly:

```python
from binaryninja import TransformSession, load

# Test basic extraction
session = TransformSession("test_container.bin")

# Process and check results
if session.process():
    print(f"Extraction complete: {session.current_context.filename}")
else:
    print("User interaction required")
    ctx = session.current_context
    if ctx.parent and ctx.parent.has_available_files:
        print(f"Available files: {ctx.parent.available_files}")
```

### Loading Extracted Files

After processing, select the context and load it as a BinaryView:

```python
# Select the final context for loading
session.set_selected_contexts(session.current_context)

# Load the extracted file
with load(session.current_view) as view:
    print(f"Loaded: {view.file.filename}")
    print(f"View type: {view.view_type}")
```

### Manual Transform Selection

When auto-detection fails (e.g., Base64 has no magic bytes), you can manually specify the transform:

```python
# Create session and process the container
session = TransformSession("archive.zip")
if not session.process():
    print("Failed to extract archive")
    return

# Get the extracted file context
extracted_ctx = session.current_context

# Manually set the transform to apply
extracted_ctx.set_transform_name("Base64")

# Process from this context to apply the transform
if not session.process_from(extracted_ctx):
    print("Failed to apply Base64 transform")
    return

# Select and load the final decoded file
final_ctx = session.current_context
session.set_selected_contexts(final_ctx)

with load(session.current_view) as bv:
    print(f"Loaded: {bv.file.filename}")
    print(f"View type: {bv.view_type}")
    print(f"Transform chain: {bv.file.virtual_path}")
```

### Transform Chaining

TransformSession automatically handles multi-layer extraction:

```python
# Process a nested container (e.g., tar.gz or tar.xz)
session = TransformSession("archive.tar.gz")

# Single process() call handles the entire chain
if session.process():
    # Load the final extracted file
    session.set_selected_contexts(session.current_context)
    with load(session.current_view) as bv:
        # View the complete extraction chain
        print(bv.file.virtual_path)
        # Example output: 'Gzip(/path/to/archive.tar.gz)::Tar()::extracted'
```

### UI Testing Modes

For interactive testing in the UI:

1. **Full Mode**: Settings → `files.container.mode` → "Full"
   - Opens your container and shows all extracted files immediately
2. **Interactive Mode**: Settings → `files.container.mode` → "Interactive"
   - Requires clicking through each level of the container hierarchy

## API Reference

For complete API documentation, see:

- [`Transform`](https://api.binary.ninja/binaryninja.transform-module.html#binaryninja.transform.Transform) - Base transform class
- [`TransformContext`](https://api.binary.ninja/binaryninja.transform-module.html#binaryninja.transform.TransformContext) - Container extraction context
- [`TransformSession`](https://api.binary.ninja/binaryninja.transform-module.html#binaryninja.transform.TransformSession) - Multi-stage extraction workflow
- [`TransformResult`](https://api.binary.ninja/binaryninja.enums-module.html#binaryninja.enums.TransformResult) - Extraction result codes
- [`TransformCapabilities`](https://api.binary.ninja/binaryninja.enums-module.html#binaryninja.enums.TransformCapabilities) - Transform capability flags