Skip to content

Commit c5500f2

Browse files
committed
[WARP] More documentation updates and python examples
1 parent 7e8d8bd commit c5500f2

10 files changed

Lines changed: 126 additions & 71 deletions

docs/guide/warp.md

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ When a function matches, we will apply the following information:
2424
- Symbol
2525
- Name
2626
- Demangled type
27-
- User defined type
27+
- User-defined type
2828
- Calling convention
2929
- Parameter names
3030
- Parameter types
3131
- Return type
32-
- User defined variables
32+
- User-defined variables
3333
- Name
3434
- Type
3535
- Comments
@@ -52,8 +52,8 @@ Files are automatically loaded from two locations when Binary Ninja starts:
5252
### Manually
5353

5454
Aside from using the signature directory you can load any WARP file manually using the command `WARP\\Load File` or via
55-
the UI sidebar, they both do the same thing. Once the file is loaded, you do not need to load it for every view, it is
56-
available globally.
55+
the UI sidebar, they both do the same thing. Once the file is loaded, you do not need to load it for every view, any view
56+
that performs function matching will have access to the loaded file.
5757

5858
???+ Info "Tip"
5959
When loading signatures you may encounter a dialog asking to "Override file target?" this happens when your file
@@ -62,35 +62,20 @@ available globally.
6262

6363
## Creating WARP Files
6464

65-
Before you actually can create these WARP files, you must identify the binary files relevant to the target binary. Once
66-
that is done, you can determine which of the following procedures is best:
65+
Before you actually can create these WARP files, you must identify the binary files relevant to the target binary. This
66+
can differ depending on the type of binary you are working with, but once you have those files you can create the files
67+
using the command `WARP\\Process` or via the UI sidebar.
6768

68-
=== "From the current view"
69-
You can create signatures for the current view by running the
70-
command `WARP\\Create\\From View` or by hitting the associated
71-
button in the WARP sidebar.
69+
![Processor Dialog](../img/warp/processor_dialog.png "Processor Dialog"){ width="600" }
7270

73-
![From View](../img/warp/create_from_view.png "From View")
74-
75-
=== "From a file"
76-
Using the command `WARP\\Create\\From File(s)` you can create
77-
a signature file for an external file. This is useful if you
78-
are generating signatures for library files (`.a`, `.lib`, `.rlib`).
79-
80-
![From File](../img/warp/create_from_file.png "From File")
81-
82-
=== "From the current project"
83-
When dealing with a large dataset, you will want a way to process
84-
files in parallel, using the `WARP\\Create\\From Project` command
85-
you can generate signatures for any set of files in a project, this
86-
includes archive formats like library files (`.a`, `.lib`, `.rlib`).
87-
88-
![From Project](../img/warp/create_from_project.png "From Project")
71+
The processor dialog will allow you to select the files you want to process, including directories and project files. To
72+
add more files, you can use the "+" button and select the files you want to add. If you have more than one file to process
73+
the worker count will control how many entries are processed in parallel.
8974

9075
???+ Info "Tip"
9176
You can also create signature files using the provided API, see the [API section](#api) for more details.
9277

93-
Both the file and project signature creation support loading the following file formats:
78+
The following file formats are supported:
9479

9580
- Binary files (`.exe`, `.so`, `.dylib`)
9681
- WARP files (`.warp`)
@@ -100,6 +85,14 @@ Both the file and project signature creation support loading the following file
10085
- Archive files (`.a`, `.lib`, `.rlib`)
10186
- The archive entry files will be extracted to a temporary directory for processing.
10287

88+
After processing is complete, you will be shown a dialog with the results, from here you can save the file to disk or commit
89+
the data to the server using the commit dialog.
90+
91+
![Report Dialog](../img/warp/report_dialog.png "Report Dialog"){ width="600" }
92+
93+
If you are trying to commit the data to the server, make sure you have [connected](#connecting) to the server first with
94+
a valid API key, otherwise you will get an error when commiting.
95+
10396
### Including specific functions
10497

10598
Sometimes you may not want to include every function but a subset, in that case you can tag functions to include with
@@ -109,9 +102,8 @@ will be prompted whether you want to keep the existing data, you will want to sa
109102

110103
### File size
111104

112-
Information in the WARP file will be deduplicated across all processed files automatically.
113-
If your files are too large, try and adjust the file data to something like "Symbols" only, and if you are looking to
114-
make the files load quicker, turn off compression.
105+
Information in the WARP file will be deduplicated across all processed files automatically. If your files are too large,
106+
try and adjust the file data to something like "Symbols" only.
115107

116108
## Networked Functionality
117109

@@ -127,7 +119,7 @@ and the default primary server is https://warp.binary.ninja. You can also give i
127119
in as your user, and have access to push data to your sources using `warp.container.serverApiKey`.
128120

129121
Once restarted, you should see a log message indicating you have connected. You can also verify connections in the WARP
130-
sidebar under the "Containers" tab which should list the provided WARP server(s) alongside any of your sources you have created.
122+
sidebar under the "Containers" tab, which should list the provided WARP server(s) alongside any of your sources you have created.
131123

132124
### Pulling Networked Data
133125

@@ -140,7 +132,7 @@ globally by modifying the setting `warp.fetcher.allowedSourceTags` as a comma se
140132
from within the server UI, either by source users or the server admin, the tags "official" and "trusted" may only be added
141133
or removed by the server admin.
142134

143-
![Fetch Dialog](../img/warp/fetch_dialog.png "Fetch Dialog")
135+
![Fetch Dialog](../img/warp/fetch_dialog.png "Fetch Dialog"){ width="600" }
144136

145137
???+ Info "Tip"
146138
Fetching of function information from the server will also be done on demand when navigating to a function for the first time
@@ -164,11 +156,18 @@ To get an API key using the website:
164156

165157
Once restarted, you should see a log message indicating you have connected as the associated user.
166158

159+
> [WARP.Plugin] Server 'https://warp.binary.ninja' connected, logged in as user 'binary-dog-4213'
160+
167161
After logging in, you can create a new source on the server by right-clicking in the container sources tab and selecting
168-
"Add Source" you can also do this via the website.
162+
"Add Source" you can also do this via the website or in the processor dialog (see below).
163+
164+
Once you have created a source, you can start pushing your information to the server by invoking the processor dialog
165+
and opening the commit dialog after processing of the data has completed.
166+
167+
![Commit Dialog](../img/warp/commit_dialog.png "Commit Dialog"){ width="600" }
169168

170-
Once you have created a source, you can start pushing your WARP files to them using the command `WARP\\Commit File` or
171-
using the button within the WARP sidebar.
169+
Each source operates as its own collection of function and type information, creating a new source is as simple as clicking
170+
the "+" button and giving it a name. The sources are synced on the server and can be managed from the website.
172171

173172
## Overwriting Matched Functions
174173

@@ -189,23 +188,32 @@ from being matched automatically in the future.
189188

190189
## API
191190

192-
To create, query and load WARP data programmatically, we provide a [Python API]. For those looking to interact with WARP
191+
To create, query, and load WARP data programmatically, we provide a [Python API]. For those looking to interact with WARP
193192
from Rust because the plugin is open source, you can depend _directly_ on the [Rust plugin], skipping the FFI entirely.
194193

195194
### Rust example (recommended)
196195

197-
This example will use the Rust API directly to generate WARP files from given inputs, it will automatically parallelize
198-
the work, so it will be much faster than the Python example.
196+
This example will use the Rust API directly to generate WARP files from given inputs, this operates with the same processor
197+
as the UI and supports all the same options.
199198

200199
Find the example [here](https://github.com/Vector35/binaryninja-api/tree/dev/plugins/warp/examples/headless).
201200

202201
### Python example
203202

203+
This example will open a binary in Binary Ninja then output a WARP signature file using the core processor API. This is the
204+
easiest way to get started with the API, as it will use the same processor as the UI and support all the same options.
205+
206+
Find the example [here](https://github.com/Vector35/binaryninja-api/tree/dev/plugins/warp/examples/create_signatures.py).
207+
208+
### Python example (advanced)
209+
204210
This example will open a binary in Binary Ninja then output a WARP signature file.
205211

206212
The flexibility of the API allows you to include or exclude any functions you want from the creation of the signature file.
213+
The cost is that it does not use the same processor as the UI and you will need to implement the same logic yourself for
214+
selecting the functions and processing in parallel.
207215

208-
Find the example [here](https://github.com/Vector35/binaryninja-api/tree/dev/plugins/warp/examples/create_signatures.py).
216+
Find the example [here](https://github.com/Vector35/binaryninja-api/tree/dev/plugins/warp/examples/create_signatures_advanced.py).
209217

210218
## Troubleshooting
211219

@@ -230,20 +238,34 @@ The function GUID will differ if the instruction highlights are not exactly the
230238

231239
When running the matcher manually, you may get a warning about no relocatable regions found; this means you have no defined
232240
sections or segments in your view. For WARP to work we must have some range of address space to work with, without it the
233-
function GUIDs will likely be inconsistent if the functions can be based at different addresses.
241+
function GUIDs will likely be inconsistent if the functions can be based at different addresses. Once you have updated the sections,
242+
or segments, you should [regenerate the function GUIDs](#regenerating-the-function-guids).
234243

235244
### "Relocatable region has a low start-address" warning
236245

237246
WARP uses relocatable regions to determine relocatable addresses encoded in instructions, if you have a relocatable region
238-
that covers a low address space, WARP may mask regular constants and other irrelevant instructions. This warning mostly
247+
that covers a low-address space, WARP may mask regular constants and other irrelevant instructions. This warning mostly
239248
affects firmware binaries (or other mapped views), if you have not rebased the view to the correct image base, then you
240-
should as it will fix this issue
249+
should as it will fix this issue. Once you have rebased the view, you should [regenerate the function GUIDs](#regenerating-the-function-guids).
241250

242251
### Failed to connect to the server
243252

244253
If you fail to connect to a WARP server, you will receive an error in the log. Outside typical network connectivity issues
245254
it is possible the provided server URL is malformed, verify that the URL looks similar to the default server URL: `https://warp.binary.ninja`
246255

256+
### Regenerating the function GUIDs
257+
258+
After updating the binary with new sections, segments, or base address, you will need to regenerate the function GUIDs to ensure
259+
they are accurate and up to date. Unfortunately, this is currently not something we can automatically do for you. To do this
260+
you will need to run something like this:
261+
262+
```python
263+
from binaryninja.warp import *
264+
for func in bv.functions:
265+
func.remove_metadata('warp_function_guid')
266+
get_function_guid(func)
267+
```
268+
247269
## Glossary
248270

249271
Here is a list of terms used and a simplified description, please see the [WARP] spec repository for a more detailed description.
@@ -252,17 +274,17 @@ Here is a list of terms used and a simplified description, please see the [WARP]
252274
A **Target** defines platform-specific information needed to filter out irrelevant WARP information.
253275

254276
### Container
255-
A **Container** stores and manages WARP data, whether in memory, on disk or over the network. Each container has its own collection of sources.
277+
A **Container** stores and manages WARP data, whether in memory, on disk, or over the network. Each container has its own collection of sources.
256278

257279
### Source
258280
A **Source** is a collection of WARP data within a container, like a file containing function and type information.
259281

260282
### Source Tag
261-
A **Source Tag** is an arbitrary string that is used for filtering fetched function data from containers, useful when dealing with larger potentially
262-
untrusted datasets.
283+
A **Source Tag** is an arbitrary string, which is used for filtering fetched function data from containers, useful when dealing with
284+
larger and potentially untrusted datasets.
263285

264286
### Function
265-
A **Function** in WARP represents the collection of metadata that we wish to transfer, such as the symbol, comments and types.
287+
A **Function** in WARP represents the collection of metadata that we wish to transfer, such as the symbol, comments, and types.
266288

267289
### Function GUID
268290
A **Function GUID** is a unique ID derived from the contents of the function, allowing matching across different binaries.
@@ -271,6 +293,13 @@ A **Function GUID** is a unique ID derived from the contents of the function, al
271293
A **Constraint** helps ensure accurate function matching by verifying specific properties are shared between functions.
272294
For example, a referenced function would be used as a constraint.
273295

296+
### File
297+
A **File** in WARP represents a collection of chunks, which are use to store function and type information. It is exposed
298+
via the API as a way to pass around the data to different parts of the application, such as the processor and the container.
299+
300+
### Chunk
301+
A **Chunk** is a collection of either function or type information, stored as a flatbuffer.
302+
274303
[WARP]: https://github.com/vector35/warp
275304
[Install Directory]: https://docs.binary.ninja/guide/#binary-folder
276305
[User Directory]: https://docs.binary.ninja/guide/#user-folder

docs/img/warp/commit_dialog.png

469 KB
Loading

docs/img/warp/create_from_file.png

-140 KB
Binary file not shown.
-121 KB
Binary file not shown.

docs/img/warp/create_from_view.png

-124 KB
Binary file not shown.

docs/img/warp/fetch_dialog.png

88.6 KB
Loading

docs/img/warp/processor_dialog.png

253 KB
Loading

docs/img/warp/report_dialog.png

637 KB
Loading

plugins/warp/examples/create_signatures.py

100755100644
Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,27 @@
22

33
import sys
44
from pathlib import Path
5-
from binaryninja import load
6-
from binaryninja.warp import WarpContainer, WarpFunction, WarpTarget
5+
from binaryninja.warp import *
76

87
def process_binary(input_file: str, output_dir: str) -> None:
98
input_path = Path(input_file)
109
output_dir = Path(output_dir)
1110
output_dir.mkdir(parents=True, exist_ok=True)
12-
bv = load(input_path)
13-
bv.update_analysis_and_wait()
14-
if not bv:
11+
output_file = output_dir / f"{input_path.stem}.warp"
12+
processor = WarpProcessor()
13+
processor.add_path(input_file)
14+
file = processor.start()
15+
if not file:
1516
return
17+
buffer = file.to_data_buffer()
18+
with open(output_file, 'wb') as f:
19+
f.write(bytes(buffer))
20+
print(f"Wrote {len(buffer)} bytes to {output_file}")
1621

17-
# For the sake of this example we are going to assume the container "User"
18-
# is available, in the future we might want to make containers constructable
19-
container = WarpContainer.by_name("User")
20-
output_file = output_dir / f"{input_path.stem}_analysis.warp"
21-
# Add the source so we can add functions to it and then commit it (write to disk)
22-
source = container.add_source(str(output_file))
23-
if source is None:
24-
print(f"Failed to create source {str(output_file)}")
25-
return
26-
27-
# NOTE: You probably want to pull the platform from the function, but for this example it's fine.
28-
target = WarpTarget(bv.platform)
29-
# NOTE: You probably want to filter for functions with actual annotations, no point to signature a function with no symbol.
30-
functions_to_warp = [WarpFunction(func) for func in bv.functions]
31-
container.add_functions(target, source, functions_to_warp)
32-
33-
# Actually write the warp file to disk.
34-
container.commit_source(source)
35-
bv.file.close()
36-
print(f"committed {len(functions_to_warp)} functions to {output_file}")
3722

3823
if __name__ == "__main__":
3924
if len(sys.argv) != 3:
4025
print(f"Usage: {sys.argv[0]} <input_binary> <output_directory>")
4126
sys.exit(1)
27+
binaryninja._init_plugins()
4228
process_binary(sys.argv[1], sys.argv[2])
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
from pathlib import Path
5+
from binaryninja import load
6+
from binaryninja.warp import WarpContainer, WarpFunction, WarpTarget
7+
8+
def process_binary(input_file: str, output_dir: str) -> None:
9+
input_path = Path(input_file)
10+
output_dir = Path(output_dir)
11+
output_dir.mkdir(parents=True, exist_ok=True)
12+
bv = load(input_path)
13+
bv.update_analysis_and_wait()
14+
if not bv:
15+
return
16+
17+
container = WarpContainer.add("Python")
18+
output_file = output_dir / f"{input_path.stem}_analysis.warp"
19+
# Add the source so we can add functions to it and then commit it (write to disk)
20+
source = container.add_source(str(output_file))
21+
if source is None:
22+
print(f"Failed to create source {str(output_file)}")
23+
return
24+
25+
# NOTE: You probably want to pull the platform from the function, but for this example it's fine.
26+
target = WarpTarget(bv.platform)
27+
# NOTE: You probably want to filter for functions with actual annotations, no point to signature a function with no symbol.
28+
functions_to_warp = [WarpFunction(func) for func in bv.functions]
29+
container.add_functions(target, source, functions_to_warp)
30+
31+
# Actually write the warp file to disk.
32+
container.commit_source(source)
33+
bv.file.close()
34+
print(f"committed {len(functions_to_warp)} functions to {output_file}")
35+
36+
if __name__ == "__main__":
37+
if len(sys.argv) != 3:
38+
print(f"Usage: {sys.argv[0]} <input_binary> <output_directory>")
39+
sys.exit(1)
40+
process_binary(sys.argv[1], sys.argv[2])

0 commit comments

Comments
 (0)